Option Explicit

'################################################################
' Descriptor
' sql 翻译器
' 用来在逻辑 sql <<-->> 物理 sql 之间进行转换
'################################################################

'################################################################
' Field
'################################################################
' ----------------------------
' 分析/语法阶段需要的内容
' ----------------------------
' entity 管理器
Private em As ISqlEntityManager

' sql token 提取器
Private tokenExtractor As SqlTokenExtractor

' token 缓存
' IObjArrayRef<SqlToken>
Private allTokenCache As IObjArrayRef

' token 缓存
' IObjArrayRef<SqlToken>
Private meanTokenCache As IObjArrayRef
' ----------------------------
' 上下文内容
' ----------------------------
' 保存括号的嵌套数量
Private parenthesisNestCount As Integer

' 缓存各组 withEnvs，key=这一组 withEnvs 所在的括号嵌套深度
' 对于每一个括号嵌套的深度，保存的格式是: 在对象栈中保存多组 withEnv
'
' 对象栈的保存方式主要是解决下面这种情况，参考: 【with 的解析方式】下的【特殊的 with 结构】
'     with()
'     (select)
'     union all
'     with()
'     (select)
' （虽然是语法错误，但是可以兼容）
' 这种情况虽然不多，但是仍然会存在
' 为了减少每次创建/销毁对象栈的消耗，每次需要从 UtilsPool 池中获取可重用的资源
'
' Map<Integer, ObjStack<IObjArrayRef<SqlTranslateWithEnv>>>
Private unconsumedWithEnvsCache As Object

'################################################################
' Constructor
'################################################################
Private Sub Class_Initialize()
    ' 1. 初始化: 括号的嵌套数量
    parenthesisNestCount = 0

    ' 2. 创建 sql token 提取器
    Set tokenExtractor = New SqlTokenExtractor
    tokenExtractor.Init
End Sub

Private Sub Class_Terminate()
    Set allTokenCache = Nothing
    Set meanTokenCache = Nothing
    Set em = Nothing
    Set tokenExtractor = Nothing
End Sub

'################################################################
' Init
'################################################################
Public Sub Init(ByRef entityManager As ISqlEntityManager)
    If entityManager Is Nothing Then
        Err.Raise 10001, Description:="parameter entityManager must be not nothing"
    End If

    Set em = entityManager
End Sub

'################################################################
' Function
'################################################################
'----------------------------------------------------------------
'sql翻译
'----------------------------------------------------------------
' 将指定sql翻译成逻辑sql
'
' @paran sql sql 片段
' @param tips 提示信息字符串
Public Function translateToLogicSql(ByRef sql As String, Optional ByRef tips As String = "") As String
    translateToLogicSql = translateSql(tips, sql, 1)
End Function

' 将指定sql翻译成物理sql
'
' @paran sql sql 片段
' @param tips 提示信息字符串
Public Function translateToPhySql(ByRef sql As String, Optional ByRef tips As String = "") As String
    translateToPhySql = translateSql(tips, sql, 0)
End Function

'################################################################
' Private Function
'################################################################
' ======================================================
' 执行转换
' ======================================================
' 执行转换
' @param tips 提示信息
' @param sql  需要转换的 sql
' @param translateType 转换的类型(逻辑->物理=0, 物理->逻辑=1)
Private Function translateSql( _
    ByRef tips As String, ByRef sql As String, _
    ByVal translateType As Integer) As String
    ' 1. 清空 token 缓存
    clearTokenCache

    ' 2. 执行分词
    '    分词后的结果保存到上下文中
    analyzeSql sql

    ' 3. 创建作用域
    Dim sqlEnv As ISqlTranslateEnv
    Set sqlEnv = createEnv(tips, meanTokenCache)
    If sqlEnv Is Nothing Then
        ' 如果无法创建一个作用域，则返回原始 sql
        translateSql = sql
        Exit Function
    End If

    ' 4. 执行名称的翻译
    sqlEnv.translate em, translateType

    ' 5. 生成翻译后的 sql
    translateSql = createTranslatedSql
End Function

' 清空 token 缓存
Private Sub clearTokenCache()
    If allTokenCache Is Nothing Then
        Set allTokenCache = UtilsArrayRef.createRealObjArrayRef(100)
    Else
        allTokenCache.clear
    End If

    If meanTokenCache Is Nothing Then
        Set meanTokenCache = UtilsArrayRef.createRealObjArrayRef(100)
    Else
        meanTokenCache.clear
    End If
End Sub

' ======================================================
' 生成结果
' ======================================================
' 5. 生成翻译后的 sql
Private Function createTranslatedSql() As String
    Dim result As String
    result = ""

    Dim i As Long
    Dim t As SqlToken
    With allTokenCache
        For i = 0 To .MaxIndex
            Set t = .Item(i)
            result = result & t.value
        Next
    End With

    createTranslatedSql = result
End Function

' ======================================================
' 执行 sql 分词
' ======================================================
Private Sub analyzeSql(ByRef sql As String)
    ' 解析 sql
    tokenExtractor.analyze sql

    ' 获取解析后的 token
    Set allTokenCache = tokenExtractor.allTokens
    Set meanTokenCache = tokenExtractor.meaningfulTokens
End Sub

' ======================================================
' 创建作用域
' ======================================================
' 创建作用域
Private Function createEnv(ByRef tips As String, ByRef meanTokenCache As IObjArrayRef) As ISqlTranslateEnv
    ' 1. 如果提供了 tips，为 tips 创建一个作用域
    If tips <> "" Then
        Set createEnv = createTableTipsEnv(tips)
    End If

    If createEnv Is Nothing Then
        Dim temp As SqlTranslateSnippetsEnv
        Set temp = New SqlTranslateSnippetsEnv
        temp.Init SqlTokenType.NO_MEAN
        Set createEnv = temp
    End If

    ' 2. 创建 sql 的作用域
    parseSQLEnv meanTokenCache, createEnv

    ' 3. 返回最外层的作用域
    ' return containerEnv
End Function

' ======================================================
' 解析sql的token并生成作用域
' ======================================================
' 扫描所有 token 并生成一整套的作用域
Private Sub parseSQLEnv(ByRef meanTokenCache As IObjArrayRef, ByRef containerEnv As ISqlTranslateEnv)
    ' 1. 创建扫描器
    Dim tokenScanner As ObjArrayRefScanner
    Set tokenScanner = New ObjArrayRefScanner
    tokenScanner.Init meanTokenCache

    ' 2. 初始括号嵌套数量
    parenthesisNestCount = 0

    ' 3. 将 scanner 定位到第一个有意义的内容
    '    有意义的内容包括: 关键字 + name
    '
    ' 3.1 如果遇到了 ( , 增加括号嵌套深度
    ' 3.2 如果遇到了 [)], 开头，则减少括号深度（如果栈中有的话）
    ' 3.3 如果遇到了 [.], [,], [;] 开头，则忽略
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        If tokenType = SqlTokenType.LEFT_PARENTHESIS Then
            ' 增加括号嵌套深度，然后扫描器向后移动
            parenthesisNestCount = parenthesisNestCount + 1
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            ' 如果发现了一个右括号 [)]，则检查括号嵌套深度
            If parenthesisNestCount > 0 Then
                parenthesisNestCount = parenthesisNestCount - 1
            End If

            ' 扫描器向后移动
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.POINT Then
            ' 如果遇到了 [.] ，则忽略
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.COMMA Then
            ' 如果遇到了 [,] 开头，则忽略
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.SEMICOLON Then
            ' 如果遇到了 [;] 开头，则忽略
            tokenScanner.toNext
        Else
            Goto while1Break
        End If
    Wend
while1Break:

    ' 4. 开始正式扫描
    Dim activeEnv As ISqlTranslateEnv
    Dim scannerIndex As Long

    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 以下几种 token，在处理后，立刻进入下一次遍历，不作后面的检查
        ' 检查辅助字符，成功后立刻进入下一次遍历，不作后面的检查
        If tokenType = SqlTokenType.SEMICOLON Then
            ' 处理 [;]
            ' 这里遇到了一个 sql 的结束，忽略，并向后推进
            tokenScanner.toNext
            ' 重置括号深度
            parenthesisNestCount = 0

            ' 延迟保存作用域
            ' (为了保证退出 [)] 时能够及时保存 with，不会在作用域生成后立刻保存，而是在当前这种作用域切换的位置进行保存)
            containerEnv.addSubEnv activeEnv

            ' 如果存在无所属的 withEnv，则全部作为子查询保存到 result，以免作为 with 干扰其他片段
            If existsUnconsumedWithEnvs Then
                alterUnconsumedWithEnvsToUncredited containerEnv
            End If

            ' 清除活动作用域
            Set activeEnv = Nothing

            GoTo matchEnvContinue
        ElseIf tokenType = SqlTokenType.LEFT_PARENTHESIS Then
            ' 处理 [(]
            parenthesisNestCount = parenthesisNestCount + 1
            tokenScanner.toNext

            GoTo matchEnvContinue
        ElseIf tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            ' 处理 [)]
            ' 这里无论如何都必须再执行一次，以防止以下这种只有 with 的情况:
            ' (with())
            Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount, True)

            ' 这里尝试退出连续出现的 [)]，并合并 with
            ' 这里不做限制，一只退出所有连续出现的 [)]
            Set activeEnv = exitRightParenthesisAndWithEnv(tokenScanner, activeEnv)

            GoTo matchEnvContinue
        ElseIf tokenType = SqlTokenType.KEY_WITH Then
            ' with xxx as ()
            parseMultiWith tokenScanner
            GoTo matchEnvContinue
        End If

        scannerIndex = tokenScanner.ActiveIndex

        If tokenType = SqlTokenType.KEY_SELECT Then
            ' select
            Set activeEnv = parseSelect(tokenScanner)
        ElseIf tokenType = SqlTokenType.KEY_UPDATE Then
            ' update table set where
            Set activeEnv = parseUpdate(tokenScanner)
        ElseIf tokenType = SqlTokenType.KEY_DELETE Then
            ' delete from table ... where
            Set activeEnv = parseDelete(tokenScanner)
        ElseIf tokenType = SqlTokenType.KEY_INSERT Then
            ' insert into table(colname) values()
            Set activeEnv = parseInsert(tokenScanner)
        ElseIf tokenType = SqlTokenType.KEY_TRUNCATE Then
            ' truncate tableName
            Set activeEnv = parseTruncate(tokenScanner)
        ElseIf tokenType = SqlTokenType.KEY_CREATE Then
            ' create table xxx ()
            ' activeEnv = parseCreate(tokenScanner)
        ElseIf tokenType = SqlTokenType.KEY_ALTER Then
            ' alter table [only] shcema.table add.... PRIMARY KEY()
            'activeEnv = parseAlter(tokenScanner)
        Else
            ' 非标准 sql
            ' 对于非标准片段，不做筛选列的匹配，所有列全部作为条件列
            Set activeEnv = parseAbnormalSnippets(tokenScanner)
        End If

        ' 如果这次处理中扫描器没有移动，则说明可能出现了语法问题
        ' 所以在这里手动推进扫描器
        If scannerIndex = tokenScanner.ActiveIndex Then
            tokenScanner.toNext
        End If

matchEnvContinue:
    Wend
matchEnvBreak:

    ' 延迟保存作用域
    ' (为了保证退出 [)] 时能够及时保存 with，不会在作用域生成后立刻保存，而是在当前这种作用域切换的位置进行保存)
    ' (在这里再次执行，以避免最后不是 [;] 时，导致作用域无法被保存的问题)
    containerEnv.addSubEnv activeEnv

    ' 如果存在无所属的 withEnv，则全部作为子查询保存到 result，以免作为 with 干扰其他片段
    ' 在这里防止这种片段中只有 with 的情况
    ' ```sql
    ' with()
    ' ```
    If existsUnconsumedWithEnvs Then
        alterUnconsumedWithEnvsToUncredited containerEnv
    End If

    ' 5. 执行真实表 --> with 表作用域的转换
    containerEnv.transFormRealTableToWithTable
End Sub

' ======================================================
' 解析sql的token并生成作用域
' ======================================================
' 为 tips 创建一个片段级作用域
Private Function createTableTipsEnv(ByRef tips As String) As ISqlTranslateEnv
    Dim result As SqlTranslateSnippetsEnv
    Set result = New SqlTranslateSnippetsEnv
    result.Init SqlTokenType.NO_MEAN

    ' 获取 sql token 提取器的内置字符串解析器
    Dim sqlStrExtractor As StringTokenExtractor
    Set sqlStrExtractor = tokenExtractor.innerStrExtractor

    ' 1. 收集 tips 各行中的有意义的 token
    ' 只处理 tips 中的表名
    ' tips 按行来处理
    sqlStrExtractor.analyze tips

    ' IObjArrayRef<IObjArrayRef<SqlToken>>
    Dim tipsMeanTokensLine As IObjArrayRef
    Dim tipsMeanTokenArr As IObjArrayRef

    Set tipsMeanTokensLine = UtilsArrayRef.createRealObjArrayRef(5)
    Set tipsMeanTokenArr = UtilsArrayRef.createRealObjArrayRef(5)

    Dim word As String
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    While sqlStrExtractor.hasToken
        ' 获取一个字符串
        word = sqlStrExtractor.getNextToken

        ' 如果发现了一个换行 或者一个 [,]，则将一扫描的这部分作为一行来处理
        ' 如果这一行中的 token 数量 = 0，则忽略
        If word = vbCrLf Or word = vbLf Or word = "," Then
            If tipsMeanTokenArr.Count > 0 Then
                tipsMeanTokensLine.Add tipsMeanTokenArr

                Set tipsMeanTokenArr = UtilsArrayRef.createRealObjArrayRef(5)
            End If

            Goto while1Continue
        End If

        ' 忽略无意义字符串
        If tokenExtractor.isNoMeanStr(word) Then
            Goto while1Continue
        End If

        ' 忽略 ()
        If word = "(" Or word = ")" Then
            Goto while1Continue
        End If

        ' 保存关键字
        If SqlHelper.isSqlKey(LCase(word)) Then
            Set token = New SqlToken
            tokenType = SqlHelper.getSqlKeyTokenType(word)
            token.Init word, tokenType

            tipsMeanTokenArr.Add token

            Goto while1Continue
        End If

        ' 保存 .
        If word = "." Then
            Set token = New SqlToken
            token.Init word, SqlTokenType.POINT

            tipsMeanTokenArr.Add token

            Goto while1Continue
        End If

        ' 将非数字字符串作为名称并缓存
        If Not UtilsString.isNumStr(word) Then
            Set token = New SqlToken
            token.Init word,  SqlTokenType.STR_NAME

            tipsMeanTokenArr.Add token
        End If
while1Continue:
    Wend

    ' 在这里保存最后一个创建的行缓存（如果这个缓存中的 token 不是 0）
    If tipsMeanTokenArr.Count > 0 Then
        tipsMeanTokensLine.Add tipsMeanTokenArr
    End If

    ' 2. 将缓存保存成扫描器
    ' completeTableName = [schema.]name [[as] name]

    ' completeTableName -> tableName
    '                    | tableName aliasName
    '      tableName    -> name
    '                    | schema.name
    '      aliasName    -> name
    '                    | as name
    '                    | ""
    '      schema       -> name
    '      name         -> str
    Dim i As Long
    Dim elem As IObjArrayRef
    Dim lineTokenScanner As ObjArrayRefScanner
    Dim env As SqlTranslateTableEnv
    With tipsMeanTokensLine
        For i = 0 To .MaxIndex
            Set elem = .Item(i)
            ' 处理每一行中的 token
            Set lineTokenScanner = New ObjArrayRefScanner
            lineTokenScanner.Init elem

            ' 如果能够匹配表作用域，则保存作用域
            Set env = parseRealTableEnv(lineTokenScanner)
            result.ISQLTranslateEnv_addTableEnv env
        Next
    End With

    Set createTableTipsEnv = result
End Function

' ======================================================
' 生成真实表的表级作用域
' ======================================================
' 匹配真实表定义，并创建表级作用域
Private Function parseRealTableEnv(ByRef tokenScanner As ObjArrayRefScanner) As SqlTranslateTableEnv
    ' 1. 获取表定义
    Dim tableInfo As SqlTranslateRealTableInfo
    Set tableInfo = parseRealTableInfo(tokenScanner)
    If tableInfo Is Nothing Then
        ' return null
        Exit Function
    End If

    ' 2. 检查是否还有 alias
    If matchAliasName(tokenScanner) Then
        tableInfo.ISqlTranslateTableInfo_setAliasNameToken tokenScanner.getNext()
    End If

    ' 3. 创建一个表级作用域来包含表定义
    Set parseRealTableEnv = New SqlTranslateTableEnv
    parseRealTableEnv.Init tableInfo
End Function

' 匹配真实表定义，并创建表信息
' 匹配内容
' completeTableName = [schema.]name
'
' completeTableName -> tableName
'                    | tableName aliasName
'      tableName    -> name
'                    | schema.name
'      schema       -> name
'      name         -> str
Private Function parseRealTableInfo(ByRef tokenScanner As ObjArrayRefScanner) As SqlTranslateRealTableInfo
    If Not tokenScanner.hasNext Then
        Set parseRealTableInfo = Nothing
        Exit Function
    End If

    Dim tableNameToken As SqlToken

    If matchTableName(tokenScanner) Then
        ' 如果匹配了 tableName，则取出表名
        Set tableNameToken = tokenScanner.getNext
    Else
        ' 如果无法匹配表名，则说明语法错误，忽略这一行
        Set parseRealTableInfo = Nothing
        Exit Function
    End If

    ' 匹配成功后，创建表定义
    Set parseRealTableInfo = New SqlTranslateRealTableInfo
    parseRealTableInfo.Init tableNameToken
End Function

' 检查下一部分是否能够匹配 tableName
' 匹配内容:
'      tableName    -> name
'                    | schema.name
Private Function matchTableName(ByRef tokenScanner As ObjArrayRefScanner) As Boolean
    If Not tokenScanner.hasNext Then
        matchTableName = False
        Exit Function
    End If

    ' 第一部分如果不是 name，则返回 false
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.STR_NAME Then
        matchTableName = False
        Exit Function
    End If

    ' 尝试匹配 schema.name
    ' 如果匹配成功，将会主动定位到 schema.name 的 name 上
    matchTableSchema tokenScanner

    matchTableName = True
End Function

' 检查下一部分实不是 table 的 schema
' 检查内容: tableName -> schema.name
Private Function matchTableSchema(ByRef tokenScanner As ObjArrayRefScanner) As Boolean
    If Not tokenScanner.hasNext Then
        matchTableSchema = False
        Exit Function
    End If

    ' 第一部分如果不是 name，则返回 false
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.STR_NAME Then
        matchTableSchema = False
        Exit Function
    End If
    tokenScanner.toNext

    ' 第二部分如果不是 . , 则返回 false
    If Not tokenScanner.hasNext Then
        tokenScanner.back

        matchTableSchema = False
        Exit Function
    End If
    Set token = tokenScanner.peek

    If token.tokenType <> SqlTokenType.POINT Then
        tokenScanner.back

        matchTableSchema = False
        Exit Function
    End If
    tokenScanner.toNext

    ' 如果第三部分不是 name ,则返回 false
    If Not tokenScanner.hasNext Then
        tokenScanner.back 2

        matchTableSchema = False
        Exit Function
    End If

    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.STR_NAME Then
        tokenScanner.back 2

        matchTableSchema = False
        Exit Function
    End If

    matchTableSchema = True
End Function

' ======================================================
' 处理别名
' ======================================================
' 匹配别名
' 匹配内容
'      aliasName    -> name
'                    | as name
Private Function matchAliasName(ByRef tokenScanner As ObjArrayRefScanner) As Boolean
    If Not tokenScanner.hasNext Then
        matchAliasName = False
        Exit Function
    End If

    ' 1. 检查是哪种模式
    ' 1.1 如果开始就是 name，则说明匹配的是 aliasName -> name ，则返回true
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.STR_NAME Then
        matchAliasName = True
        Exit Function
    End If

    ' 1.2 如果开始也不是 as，则返回 false，没有匹配任何一种情况
    If token.tokenType <> SqlTokenType.KEY_AS Then
        matchAliasName = False
        Exit Function
    End If
    tokenScanner.toNext

    ' 2. 到这里说明开头是 as，匹配的是 aliasName -> name
    Set token = tokenScanner.peek
    ' 2.1 如果 token 不是 name，则返回 false
    If token.tokenType <> SqlTokenType.STR_NAME Then
        ' 这里不需要回退
        ' 执行到这里，出现的情况是: 存在 as，但是后面的别名不存在
        ' 【说明存在语法错误，这里直接忽略】!!!!!
        ' tokenScanner.back()
        matchAliasName = False
        Exit Function
    End If

    matchAliasName = True
End Function

' ======================================================
' 处理 select 片段
' ======================================================
' 将 with 保存到指定作用域
'
' @optional-parma needAllGroup 是否需要保存指定深度下的所有组的 withEnvs
'        needAllGroup = false, 只保存指定深度栈顶的那一组 withEnvs
'        needAllGroup = true, 保存指定深度栈顶的每一种 withEnvs。并且将为每一组创建一个包装级作用域，以表示 with 之间的父子关系
Private Function saveWithEnvsToEnv(ByRef env As ISqlTranslateEnv, ByVal envNestCount As Integer, Optional ByVal needAllGroup As Boolean = False) As ISqlTranslateEnv
    Set saveWithEnvsToEnv = env

    ' 1. 检查指定深度下的 withEnvs 组数
    Dim withGroupCount As Integer
    withGroupCount = getUnconsumedWithEnvsGroupCount(envNestCount)
    If withGroupCount = 0 Then
        Exit Function
    End If

    ' 2. 如果当前的 [作用域] == null
    '    创建一个 [包装级作用域]，并将其设置为 [作用域]
    Dim temp As SqlTranslateWrapperEnv
    If saveWithEnvsToEnv Is Nothing Then
        Set temp = New SqlTranslateWrapperEnv
        Set saveWithEnvsToEnv = temp
    End If

    ' 3. 将 withEnvs 保存到 [作用域]
    '     1. 检查当前的 [作用域] 内是否存在 with
    '         - 如果存在，则将 [作用域] 转换成 [包装级作用域]，并将其设置为 [作用域]
    '     2. 将 with 保存到 [作用域] 中
    If saveWithEnvsToEnv.hasWithEnv Then
        Set temp = New SqlTranslateWrapperEnv
        temp.setWrappedEnv saveWithEnvsToEnv
        Set saveWithEnvsToEnv = temp
    End If

    If Not needAllGroup Then
        ' 如果当前作用域中已经存在 with，则需要进行包装
        If saveWithEnvsToEnv.hasWithEnv Then
            Set temp = New SqlTranslateWrapperEnv
            temp.setWrappedEnv saveWithEnvsToEnv
            Set saveWithEnvsToEnv = temp
        End If

        saveWithEnvsToEnv.addWithEnvs popUnconsumedWithEnvs(envNestCount)
    Else
        While withGroupCount > 0
            ' 每多一组 with，就需要在外部包装一层，以保证 with 的层次关系
            If saveWithEnvsToEnv.hasWithEnv Then
                Set temp = New SqlTranslateWrapperEnv
                temp.setWrappedEnv saveWithEnvsToEnv
                Set saveWithEnvsToEnv = temp
            End If

            saveWithEnvsToEnv.addWithEnvs popUnconsumedWithEnvs(envNestCount)

            withGroupCount = withGroupCount - 1
        Wend
    End If
End Function

' 合并 [作用域栈] 栈顶 + 活动作用域
Private Function mergeEnv(ByRef envStack As ObjStack, ByRef envNestCountStack As PrimitiveStack, ByRef env As ISqlTranslateEnv, ByVal envNestCount As Integer) As ISqlTranslateEnv
    ' 1. 检查: 如果 [作用域栈] 中的元素数量 = 0，不做处理
    If envStack.Count = 0 Then
        Set mergeEnv = env
        Exit Function
    End If

    If envNestCountStack.peek < envNestCount Then
        ' 2.1 如果 [作用域栈] 的栈顶深度 < 当前的括号嵌套深度 (说明当前进入更深一层的嵌套中)
        Set mergeEnv = env
        Exit Function
    Else
        ' 2.2 检查: 如果 [作用域栈] 的栈顶深度 >= 当前的括号嵌套深度 (说明当前是同一级别的作用域 union)
        ' 合并 [作用域栈] 栈顶 + [活动作用域]
        envNestCountStack.pop
        Set mergeEnv = unionEnv(envStack.pop, env)
        Exit Function
    End If
End Function

' 将两个作用域结合成 union 作用域
Private Function unionEnv(ByRef first As ISqlTranslateEnv, ByRef second As ISqlTranslateEnv) As ISqlTranslateEnv
    If second Is Nothing Then
        ' 1. 如果 second == null, 直接返回 first
        Set unionEnv = first
        Exit Function
    Else
        ' 2. 否则合并作用域: result = first + second
        If Typeof first Is SqlTranslateUnionEnv Then
            ' 2.1 如果 [作用域栈] 栈顶 已经是 union 作用域了
            ' [作用域栈] 栈顶 直接保存 [活动作用域]
            ' 设置 [活动作用域] = [作用域栈] 栈顶
            first.addSubEnv second
            Set unionEnv = first
            Exit Function
        Else
            ' 2.2
            '   创建一个新的 union 作用域
            '   保存 [作用域栈] 栈顶
            '   保存 [活动作用域]
            '   设置 [活动作用域] = union 作用域
            Dim result As SqlTranslateUnionEnv
            Set result = New SqlTranslateUnionEnv
            result.ISqlTranslateEnv_addSubEnv first
            result.ISqlTranslateEnv_addSubEnv second

            Set unionEnv = result
            Exit Function
        End If
    End If
End Function

' 解析 select sql片段，并生成作用域
' 需要匹配的内容
'     selectStatement --> select {union [all]} subSelect | subSelect
'           subSelect --> (subSelect)
'
' 转换后的内容
'     select --> subSelect R
'          R --> {union [all]} select R | E
'     subSelect --> (subSelect)
' @param minNest 退出 [)] 时的最小深度边界。如果提前结束了，则忽略这个值
' @param needMinNestWith 是否需要吸收最小深度边界上的 withEnvs
Public Function parseSelect(ByRef tokenScanner As ObjArrayRefScanner, Optional ByVal minNest As Integer = -1000, Optional ByVal needMinNestWith As Boolean = True) As ISqlTranslateEnv
    ' 检查开始标记
    If Not tokenScanner.hasNext Then
        'return null
        Exit Function
    End If

    ' 1. 在开始处，需要循环处理可能会出现的 [(] + [with]
    parseMultiWith tokenScanner

    If Not tokenScanner.hasNext Then
        'return null
        Exit Function
    End If

    ' 2. 尝试解析一个 select
    '     select --> subSelect R
    Dim activeEnv As ISqlTranslateEnv
    Set activeEnv = parseSubSelect(tokenScanner)

    ' 2.2 尝试退出后面连续出现的 [)]
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        ' 2.2.1 遍历 token
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 2.2.2 如果 token 是 union
        If tokenType = SqlTokenType.KEY_UNION Then
            ' 2.2.2.1 检查, 如果当前括号嵌套深度存在可用的 withEnvs 组数 > 1，则将栈顶的这组 withEnvs 保存到 [活动作用域]
            If getUnconsumedWithEnvsGroupCount(parenthesisNestCount) > 1 Then
                Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount)
            End If

            ' 2.2.2.2 退出遍历
            GoTo outWithBreak
        End If

        ' 2.2.3 检查
        If parenthesisNestCount > minNest Then
            ' 2.2.3.1 如果当前括号嵌套深度 > 参数[最小深度边界], 则
            '         尝试将这一深度的所有 withEnvs 都保存到 [活动作用域] 内
            Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount, True)
        ElseIf needMinNestWith Then
            ' 2.2.3.2 否则: 当前括号嵌套深度 <= 参数[最小深度边界]，继续检查: 如果 needMinNestWith = true (需要吸收边界的 with)，则
            '         尝试将这一深度的所有 withEnvs 都保存到 [活动作用域] 内
            Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount, True)
        End If

        ' 2.2.4 如果 token 是 [)]
        If tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            ' 2.2.4.1 括号嵌套深度 - 1
            parenthesisNestCount = parenthesisNestCount - 1

            ' 2.2.4.2. 跳过当前 [)]
            '   这一层的 withEnv 保存推迟到下一次遍历中处理，以保证 [)] 后面是 union 时，可以及时停止保存操作
            tokenScanner.toNext

            ' 2.2.4.3. 跳转到 1 继续遍历
        Else
            ' 2.2.5 如果是其他 token (这说明整体的解析已经结束了)
            '       直接退出当前处理，并返回 [活动作用域]
            Set parseSelect = activeEnv
            Exit Function
        End If
    Wend
outWithBreak:

    ' 3. 检查: 如果已经没有 token 了
    If Not tokenScanner.hasNext Then
        ' 3.1 检查 （在这里检查，以防止 `parseSubSelect` 解析完成后，没有 token 的情况）
        If parenthesisNestCount > minNest Then
            ' 3.1.1 如果当前括号嵌套深度 > 参数[最小深度边界], 则
            '       尝试将这一深度的所有 withEnvs 都保存到 [活动作用域] 内
            Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount, True)
        ElseIf needMinNestWith Then
            ' 3.1.2 否则: 当前括号嵌套深度 <= 参数[最小深度边界]，继续检查: 如果 needMinNestWith = true (需要吸收边界的 with)，则
            '      尝试将这一深度的所有 withEnvs 都保存到 [活动作用域] 内
            Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount, True)
        End If

        ' 3.2 立刻结束，并返回 [活动作用域]
        Set parseSelect = activeEnv
        Exit Function
    End If

    ' 4. 特殊情况处理
    ' 4.1 这里需要处理的内容
    '     ```sql
    '     with()
    '     with()   --> 这里应该在外部在包装一层 () 才是正确的语法。这个 with 属于下面的 select
    '     select...
    '     union
    '     select...
    '     ```
    ' 4.2 检查: 如果当前括号嵌套深度包含的 withEnvs 数量 > 1，则将栈顶的那个 withEnvs 保存到 [活动作用域] 内
    If getUnconsumedWithEnvsGroupCount(parenthesisNestCount) > 1 Then
        Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount)
    End If

    ' 5. union 解析前的初始化
    ' 5.1 初始化 [作用域栈]
    '     - 需要初始化的内容
    '         1. 作用域深度栈(envNestCountStack) , 保存每个作用域所在的括号嵌套深度
    '         2. 作用域对象栈(envStack) , 保存作用域对象
    '     - 每次需要同时向这两个栈中写入内容，以保证两个栈是同步的
    Dim envNestCountStack As PrimitiveStack
    Dim envStack As ObjStack
    Set envNestCountStack = UtilsPool.getPrimitiveStack
    Set envStack = UtilsPool.getObjStack

    ' 6. 处理剩余的 union 部分
    ' 6.1 遍历 token
    While tokenScanner.hasNext
        ' 6.2 获取一个 token
        Set token = tokenScanner.peek

        ' 6.3 检查 token 是不是 union
        If token.tokenType <> SqlTokenType.KEY_UNION Then
            ' 6.3.1 如果不是 union ，则立刻退出遍历 ([;] 将会在这里退出)
            GoTo matchUnionBreak
        Else
            ' 6.3.2 如果是 union
            ' 6.3.2.1 跳过当前 token
            tokenScanner.toNext

            ' 6.3.2.2 如果已经没有 token 了，则退出
            If Not tokenScanner.hasNext Then
                GoTo matchUnionBreak
            End If

            ' 6.3.2.3 否则，检查下一个 token。如果是 all, 则跳过 all 这个 token
            Set token = tokenScanner.peek
            If token.tokenType = SqlTokenType.KEY_ALL Then
                tokenScanner.toNext

                ' 跳过 token 之后，检查: 如果扫描器没有 token 了，则退出遍历
                If Not tokenScanner.hasNext Then
                    GoTo matchUnionBreak
                End If
            End If
        End If

        ' 6.4 尝试解析一个 select
        ' 6.4.1 检查, 如果当前括号嵌套深度存在可用的 withEnvs 组数 > 1，则将栈顶的这组 withEnvs 保存到 [活动作用域]
        '       这里不需要检查 [活动作用域] 的状态，`saveWithEnvsToEnv` 内会自动检查的
        If getUnconsumedWithEnvsGroupCount(parenthesisNestCount) > 1 Then
            Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount)
        End If

        ' 6.4.2 如果 [活动作用域] != null，则保存到 [作用域栈] 栈顶
        If Not activeEnv Is Nothing Then
            envNestCountStack.push parenthesisNestCount
            envStack.push activeEnv
        End If

        ' 6.4.3 由 `parseSubSelect` 解析一个 select 作用域，并保存为 [活动作用域]
        Set activeEnv = parseSubSelect(tokenScanner)

        ' 6.5 尝试退出 select 后面的 [)]
        ' 6.5.1 遍历 token
        While tokenScanner.hasNext
            ' 6.5.2 获取一个 token
            Set token = tokenScanner.peek
            tokenType = token.tokenType

            ' 6.5.3 如果 token 是 union, 则退出遍历
            If tokenType = SqlTokenType.KEY_UNION Then
                ' 6.5.3.1 合并作用域，并将合并后的作用域设置为 [活动作用域]
                Set activeEnv = mergeEnv(envStack, envNestCountStack, activeEnv, parenthesisNestCount)

                ' 6.5.3.2 检查, 如果当前括号嵌套深度存在可用的 withEnvs 组数 > 1，则将栈顶的这组 withEnvs 保存到 [活动作用域]
                If getUnconsumedWithEnvsGroupCount(parenthesisNestCount) > 1 Then
                    Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount)
                End If

                ' 6.5.3.3 退出遍历
                GoTo matchSubSelectBreak
            End If

            ' 6.5.4 合并作用域，并将合并后的作用域设置为 [活动作用域]
            Set activeEnv = mergeEnv(envStack, envNestCountStack, activeEnv, parenthesisNestCount)

            ' 6.5.5 检查
            If parenthesisNestCount > minNest Then
                ' 6.5.5.1 如果当前括号嵌套深度 > 参数[最小深度边界], 则
                '       尝试将这一深度的所有 withEnvs 都保存到 [活动作用域] 内
                Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount, True)
            ElseIf needMinNestWith Then
                ' 6.5.5.2 否则: 当前括号嵌套深度 <= 参数[最小深度边界]，继续检查: 如果 needMinNestWith = true (需要吸收边界的 with)，则
                '      尝试将这一深度的所有 withEnvs 都保存到 [活动作用域] 内
                Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount, True)
            End If

            ' 6.5.6 如果 token 是 [)]
            If tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
                ' 6.5.6.1 括号嵌套深度 - 1
                parenthesisNestCount = parenthesisNestCount - 1

                ' 6.5.6.2. 跳过当前 [)]
                '          这一层的 withEnv 保存推迟到下一次遍历中处理，以保证 [)] 后面是 union 时，可以及时停止保存操作
                tokenScanner.toNext

                ' 6.5.6.3. 跳转到 1 继续遍历
            Else
                ' 6.5.7 如果是其他 token，则退出遍历
                GoTo matchSubSelectBreak
            End If
        Wend
matchSubSelectBreak:
    Wend
matchUnionBreak:

    ' 7. 收尾
    ' 7.1 暂存 [活动括号深度] = 当前括号深度
    Dim activeNestCount As Integer
    activeNestCount = parenthesisNestCount

    ' 7.2 如果 [作用域栈] 的数量 > 0
    If envStack.Count > 0 Then
        ' 7.2.1 不断遍历，直到 [作用域栈] 为空
        ' 7.2.2 检查
        ' 7.2.2.1 如果 [作用域栈] 栈顶的嵌套深度 >= [活动括号深度]
        If envNestCountStack.peek >= activeNestCount Then
            ' 7.2.2.1.1 合并作用域，并设置 [活动作用域] = 合并结果
            Set activeEnv = unionEnv(envStack.pop, activeEnv)
            ' 7.2.2.1.2 [活动括号深度] 不变
            envNestCountStack.pop
        Else
            ' 7.2.2.2 如果 [作用域栈] 栈顶的嵌套深度 < [活动括号深度]
            ' 7.2.2.2.1 检查: 如果 [活动括号深度] 存在可用的 withEnvs，则将这一深度的所有 withEnvs 都保存到 [活动作用域] 内
            Set activeEnv = saveWithEnvsToEnv(activeEnv, activeNestCount, True)

            ' 7.2.2.2.2 设置 [活动括号深度] = [作用域栈] 栈顶的嵌套深度
            activeNestCount = envNestCountStack.pop()

            ' 7.2.2.2.3 合并作用域，并设置 [活动作用域] = 合并结果
            Set activeEnv = unionEnv(envStack.pop, activeEnv)
        End If
    End If

    ' 7.3 检查
    If parenthesisNestCount > minNest Then
        ' 7.3.1 如果[活动括号深度] > 参数[最小深度边界], 则
        '       尝试将这一深度的所有 withEnvs 都保存到 [活动作用域] 内
        Set activeEnv = saveWithEnvsToEnv(activeEnv, activeNestCount, True)
    ElseIf needMinNestWith Then
        ' 7.3.2 否则: [活动括号深度] <= 参数[最小深度边界]，继续检查: 如果 needMinNestWith = true (需要吸收边界的 with)，则
        '      尝试将这一深度的所有 withEnvs 都保存到 [活动作用域] 内
        Set activeEnv = saveWithEnvsToEnv(activeEnv, activeNestCount, True)
    End If

    ' 7.4 结束，并返回 [活动作用域]

    ' 结束前将栈归还给对象池
    UtilsPool.releasePrimitiveStack envNestCountStack
    UtilsPool.releaseObjStack envStack

    Set parseSelect = activeEnv
End Function

' 解析 select sql片段，并生成作用域
' 需要匹配的内容
'     subSelect --> (subSelect)
'
' 需要匹配的内容
'    select selectItems
'    [from tableInfo]
'    [[left[outer]|right[outer]|inner]join tableInfo [condition]]
'    [where conditions]
'    [having col]
'    [group by col]
'    [order by col]
'    [limit .. offset ... ]
'
Private Function parseSubSelect(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateEnv
    If Not tokenScanner.hasNext Then
        'return null
        Exit Function
    End If

    ' 1. 记录括号嵌套深度: [起始括号嵌套深度 = nestCountBackup]
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 2. 在开始处，需要循环处理可能会出现的 [(] + [with]
    parseMultiWith tokenScanner

    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 3. 检查第一个 token 是不是 select
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_SELECT Then
        ' 3.1 如果不是 select, 则立刻停止，返回一个 null
        'return null
        Exit Function
    End If

    ' 3.2 如果是 select
    ' 3.2.1 跳过这个 token
    tokenScanner.toNext

    ' 3.2.2 创建一个片段级作用域 (selectEnv) 作为:[活动作用域 = activeEnv]
    Dim activeEnv As SqlTranslateSnippetsEnv
    Set activeEnv = New SqlTranslateSnippetsEnv
    activeEnv.Init SqlTokenType.KEY_SELECT

    ' 3.2.3 检查，如果已经没有 token 了，则返回 [活动作用域 = activeEnv]
    If Not tokenScanner.hasNext Then
        Set parseSubSelect = activeEnv
        Exit Function
    End If

    ' 4. 记录括号嵌套深度: [select 开始时括号嵌套深度 = selectStartNestCount]
    Dim selectStartNestCount As Integer
    selectStartNestCount = parenthesisNestCount

    ' 5. 收集筛选字段
    ' 初始化标记: 片段是否已结束
    Dim snippetsIsEnd As Boolean
    ' 初始化标记: 是否已经碰到 select 边界
    Dim inSelectBoundary As Boolean
    ' 初始化标记: 发现了 union
    Dim foundUnion As Boolean

    ' 5.1 遍历 token
    Dim tokenType As SqlTokenType
    Dim colInfo As ISqlTranslateColInfo
    Dim scannerIndex As Long

    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        If tokenType = SqlTokenType.SEMICOLON Then
            ' 5.2 如果是 [;], 说明当前 sql 片段已经结束，立刻结束遍历
            '     - 保存标记: [片段已结束 = snippetsIsEnd = true]
            snippetsIsEnd = True
            GoTo selectColBreak
        ElseIf tokenType = SqlTokenType.COMMA Then
            ' 5.3 如果是 [,], 则回到 1 继续
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            ' 5.4 如果是 [)], 检查
            ' 5.4.1 如果: 当前的括号嵌套数量 <= [select 开始时括号嵌套深度 = selectStartNestCount]
            If parenthesisNestCount <= selectStartNestCount Then
                ' 1. 保存标记: [已经碰到 select 边界 = inSelectBoundary]
                inSelectBoundary = True

                ' 2. 立刻结束遍历
                '     - 说明已经碰到了这个 select 的边界
                GoTo selectColBreak
            Else
                ' 5.4.2 否则
                ' 5.4.2.1 括号嵌套深度 - 1
                ' 5.4.2.2 然后跳过这个 token
                parenthesisNestCount = parenthesisNestCount - 1
                tokenScanner.toNext
            End If
        ElseIf SqlHelper.tokenTypeIsInSelectStartActionSqlKey(tokenType) Then
            ' 5.5 如果是 select 筛选行为关键字，则扫描器向前推进，然后回到 1 继续
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.KEY_UNION Then
            ' 5.6 如果是 union，则立刻结束
            '     - 保存标记: [发现了 union = foundUnion = true]
            foundUnion = True
            GoTo selectColBreak
        ElseIf SqlHelper.tokenTypeIsSelectItemStopSqlKey(tokenType) Then
            ' 5.7 如果遇到了使筛选项匹配停止的关键字，则退出遍历，进行下一个阶段的解析
            GoTo selectColBreak
        Else
            ' 5.8 遇到其他内容则收集筛选字段
            scannerIndex = tokenScanner.ActiveIndex

            Set colInfo = parseSelectColInfo(tokenScanner)
            activeEnv.ISqlTranslateEnv_addSelectColInfo colInfo

            ' 收集之后，检查:
            '     - 如果这次处理中扫描器没有移动，则说明可能出现了语法问题，所以需要手动推进扫描器
            If scannerIndex = tokenScanner.ActiveIndex Then
                tokenScanner.toNext
            End If
        End If
    Wend
selectColBreak:

    ' 6. 检查结束状态
    If Not tokenScanner.hasNext Then
        ' 6.1 如果已经没有 token 了

        ' 6.1.1. 检查，如果当前括号深度内存在可用的 withEnvs, 将栈顶的那组 with 保存到 [活动作用域 = activeEnv]
        Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount)

        ' 6.1.2. 然后结束，并返回 [活动作用域 = activeEnv]
        Set parseSubSelect = activeEnv
        Exit Function
    ElseIf snippetsIsEnd Then
        ' 6.2 如果: [片段已结束 = snippetsIsEnd = true]，则

        ' 6.2.1 检查，如果当前括号深度内存在可用的 withEnvs, 将栈顶的那组 with 保存到 [活动作用域 = activeEnv]
        Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount)

        ' 6.2.2. 然后结束，并返回 [活动作用域 = activeEnv]
        Set parseSubSelect = activeEnv
        Exit Function
    ElseIf foundUnion Then
        ' 6.3 如果: [发现了 = foundUnion = true]
        '     立刻结束，并返回 [活动作用域 = activeEnv]
        Set parseSubSelect = activeEnv
        Exit Function
    End If

    ' 7. 检查标记，如果: [已经碰到 select 边界 = inSelectBoundary = true]
    '    - 说明现在 select 的解析已经结束，需要开始退出。跳转到 10
    If Not inSelectBoundary Then
        ' 8. 收集子级作用域的内容 + 当前作用域内条件、分组、排序等位置上的列
        While tokenScanner.hasNext
            scannerIndex = tokenScanner.ActiveIndex

            ' 8.1 遍历 token
            Set token = tokenScanner.peek
            tokenType = token.tokenType

            If tokenType = SqlTokenType.SEMICOLON Then
                ' 8.2 如果是 [;], 说明当前 sql 片段已经结束，立刻结束遍历
                '     - 保存标记: [片段已结束 = snippetsIsEnd = true]
                snippetsIsEnd = True
                GoTo otherComponentBreak
            ElseIf tokenType = SqlTokenType.LEFT_PARENTHESIS Then
                ' 8.3 如果是 [(]
                ' 8.3.1 增加嵌套数量
                parenthesisNestCount = parenthesisNestCount + 1
                ' 8.3.2 然后扫描器向后移动
                tokenScanner.toNext
            ElseIf tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
                ' 8.4 如果是 [)], 检查

                If parenthesisNestCount <= selectStartNestCount Then
                    ' 8.4.1 如果: 当前的括号嵌套数量 <= [select 开始时括号嵌套深度 = selectStartNestCount] ，立刻结束遍历
                    '     - 说明已经碰到了这个 select 的边界
                    GoTo otherComponentBreak
                Else
                    ' 8.4.2 否则
                    ' 8.4.2.1. 括号嵌套深度 - 1
                    parenthesisNestCount = parenthesisNestCount - 1
                    ' 8.4.2.2. 然后跳过这个 token
                    tokenScanner.toNext
                End If
            ElseIf tokenType = SqlTokenType.KEY_UNION Then
                ' 8.5 如果是 union，则立刻结束
                '     - 保存标记: [发现了 union = foundUnion = true]
                foundUnion = True
                GoTo otherComponentBreak
            ElseIf tokenType = SqlTokenType.KEY_FROM Then
                ' 8.6 如果是 from 开始的，则匹配 From 作用域
                ' from
                parseFrom activeEnv, tokenScanner
            ElseIf SqlHelper.tokenTypeIsJoinStartSqlKey(tokenType) Then
                ' 8.7 如果是 left / right / inner / join 开始，则尝试去匹配 join 作用域
                parseJoinFactor activeEnv, tokenScanner
            ElseIf tokenType = SqlTokenType.KEY_WHERE Then
                ' 8.8 如果是 where，则尝试匹配 where 后条件中的筛选列，完成后回到 1 继续
                parseWhereColInfos activeEnv, tokenScanner
            ElseIf tokenType = SqlTokenType.KEY_GROUP_BY Then
                ' 8.9 如果是 GROUP BY，匹配内部的列，完成后回到 1 继续
                parseGroupByColInfos activeEnv, tokenScanner
            ElseIf tokenType = SqlTokenType.KEY_HAVING Then
                ' 8.10 如果是 HAVING，匹配内部的列，完成后回到 1 继续
                parseHavingColInfos activeEnv, tokenScanner
            ElseIf tokenType = SqlTokenType.KEY_ORDER_BY Then
                ' 8.11 如果是 ORDER BY，匹配内部的列，完成后回到 1 继续
                parseOrderByColInfos activeEnv, tokenScanner
            ElseIf tokenType = SqlTokenType.KEY_LIMIT Then
                ' 8.12 如果是 LIMIT，则跳过相关的内容，完成后回到 1 继续
                skipLimit tokenScanner
            Else
                ' 8.13 如果是其他内容，则按照非标准条件列来处理
                parseOtherColInfos activeEnv, tokenScanner
            End If

            ' 8.14 每次遍历后，检查
            '     - 如果这次处理中扫描器没有移动，则说明可能出现了语法问题，所以需要手动推进扫描器
            If scannerIndex = tokenScanner.ActiveIndex Then
                tokenScanner.toNext
            End If
        Wend
otherComponentBreak:

        ' 9. 检查结束状态
        If Not tokenScanner.hasNext Then
            ' 9.1 如果已经没有 token 了
            ' 9.1.1 检查，如果当前括号深度内存在可用的 withEnvs, 将栈顶的那组 with 保存到 [活动作用域 = activeEnv]
            Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount)

            ' 9.1.2 然后结束，并返回 [活动作用域 = activeEnv]
            Set parseSubSelect = activeEnv
            Exit Function
        ElseIf snippetsIsEnd Then
            ' 9.2 如果: [片段已结束 = snippetsIsEnd]，则
            ' 9.2.1 检查，如果当前括号深度内存在可用的 withEnvs, 将栈顶的那组 with 保存到 [活动作用域 = activeEnv]
            Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount)

            ' 9.2.2 然后结束，并返回 [活动作用域 = activeEnv]
            Set parseSubSelect = activeEnv
            Exit Function
        ElseIf foundUnion Then
            ' 9.3 如果: [发现了 = foundUnion]
            '     立刻结束，并返回 [活动作用域 = activeEnv]
            Set parseSubSelect = activeEnv
            Exit Function
        End If
    End If

    ' 10. 尝试退出括号作用域
    '     (到达这里时，可以保证，现在的括号嵌套深度一定等于 selectStartNestCount)
    If parenthesisNestCount <= nestCountBackup Then
        ' 10.1 如果括号深度 <= [起始括号嵌套深度 = nestCountBackup]，则退出遍历
        Set parseSubSelect = activeEnv
        Exit Function
    End If

    ' 10.2 遍历 token
    While tokenScanner.hasNext
        ' 10.2.1 如果括号深度 <= [起始括号嵌套深度 = nestCountBackup]，则退出遍历
        '    - 在这里不处理 withEnv，因为已经到达了边界，无法确定当前括号深度下的 withEnv 是当前作用的，还是 union 整体的，这需要在外层进行检查
        If parenthesisNestCount <= nestCountBackup Then
            GoTo outParenthesisBreak
        End If

        ' 10.2.2 获取一个 token
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 10.2.3 如果 token 不是 [)] ，则退出遍历
        '     - 这里先不处理括号嵌套深度，以保证最后一个 [)] 以外的层次都可以被处理
        '     - 对于最后一层 [)] 或者 select 完全没有 [)] 的情况，无法在当前方法内判断 withEnv 是属于当前作用域还是属于 union 作用域，这只能在外层方法中处理
        If tokenType <> SqlTokenType.RIGHT_PARENTHESIS Then
            GoTo outParenthesisBreak
        End If

        ' 10.2.4 检查，如果当前括号深度内存在可用的 withEnvs, 将栈顶的那组 with 保存到 [活动作用域 = activeEnv]
        Set activeEnv = saveWithEnvsToEnv(activeEnv, parenthesisNestCount)

        ' 10.2.5 处理括号嵌套深度
        ' 10.2.5.1 括号嵌套深度 - 1
        parenthesisNestCount = parenthesisNestCount - 1
        ' 10.2.5.2 跳过当前 [)]
        '     - ( 这一层的 withEnv 的保存操作在下一次遍历中处理 )
        tokenScanner.toNext
    Wend
outParenthesisBreak:

    ' 11. 返回 [活动作用域 = activeEnv]
    Set parseSubSelect = activeEnv
End Function

' ======================================================
' 解析连续出现的多个表定义
' ======================================================
' 参考: https://dev.mysql.com/doc/refman/8.1/en/join.html
' 匹配可能出现的连续多个表定义
' 需要匹配的内容
'   table_references -> tableFactor [, tableFactor]
'
Private Sub parseTableReferences(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 尝试匹配第一个套表定义
    parseTableReference env, tokenScanner

    ' 3. 尝试匹配剩余的表定义
    Dim token As SqlToken
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        ' 3.1 跳过 [,]
        '     在这里兼容语法异常，跳过连续出现的 [,]
        If token.tokenType = SqlTokenType.COMMA Then
            Do
                tokenScanner.toNext

                If Not tokenScanner.hasNext Then
                    GoTo matchTokenBreak
                End If

                Set token = tokenScanner.peek
            Loop While token.tokenType = SqlTokenType.COMMA
        Else
            GoTo matchTokenBreak
        End If

        ' 3.2 匹配下一套表定义
        parseTableReference env, tokenScanner
    Wend
matchTokenBreak:

End Sub


' 参考: https://dev.mysql.com/doc/refman/5.7/en/join.html
' 匹配可能出现的连续多个表定义
' 需要匹配的内容
'  table_reference -> table_factor | joined_table
'     joined_table -> table_reference joinFactor
'
' 反转公式
'  table_reference -> table_factor R
'                R ->   joinFactor R | E
'
' 因为需要给予存在语法错误的 sql 充分的容忍度
' 所以 join 的部分单独处理，所以这里只是处理连续出现的表定义
Private Sub parseTableReference(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 尝试匹配第一个表定义
    Dim tableInfo As ISqlTranslateTableInfo
    Set tableInfo = parseTableFactor(tokenScanner)
    If Not tableInfo Is Nothing Then
        ' 3. 生成作用域
        Dim tableEnv As SqlTranslateTableEnv
        Set tableEnv = New SqlTranslateTableEnv
        tableEnv.Init tableInfo

        If env.hasMainEnv Then
            env.addTableEnv tableEnv
        Else
            env.setMainEnv tableEnv
        End If
    End If

    ' 3. 尝试处理后面连续出现的 joinFactor
    Dim scannerActiveIndex As Long
    While tokenScanner.hasNext
        scannerActiveIndex = tokenScanner.ActiveIndex

        parseJoinFactor env, tokenScanner

        ' 如果扫描器的活动索引没有发生移动，则立刻结束
        If scannerActiveIndex = tokenScanner.ActiveIndex Then
            GoTo joinFactorBreak
        End If
    Wend
joinFactorBreak:
End Sub

' ======================================================
' 解析 from
' ======================================================
' 解析 from
' 需要匹配的内容
' fromSnippets -> from tableReferences
Private Sub parseFrom(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 如果当前 token 不是 from，则立刻停止
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_FROM Then
        Exit Sub
    End If
    tokenScanner.toNext

    ' 3. 匹配后面可能会出现的连续多个表定义
    parseTableReferences env, tokenScanner
End Sub

' ======================================================
' 解析表定义
' ======================================================
' 解析表定义
' 参考: https://dev.mysql.com/doc/refman/8.1/en/join.html
' 需要匹配的内容
'
' table_factor -> tbl_name [PARTITION (partition_names)] [[AS] alias] [index_hint_list]
'               | table_subquery [AS] alias
'
' TODO table_factor ->  [LATERAL] table_subquery [AS] alias [(col_list)]
Private Function parseTableFactor(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateTableInfo
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 2. 过滤掉开始时的 [(]
    consumeLeftParenthesis tokenScanner

    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 3. 开始检查 tableExpr
    Dim token As SqlToken
    Set token = tokenScanner.peek

    ' 保存这一次匹配到的表的类型
    Dim matchRealTable As Boolean
    matchRealTable = False

    Dim result As ISqlTranslateTableInfo
    If token.tokenType = SqlTokenType.STR_NAME Then
        ' 3.1 如果是下一个 token 是 STR_NAME ，则匹配 tableExpr -> name
        Set result = parseRealTableInfo(tokenScanner)

        ' 匹配 partition
        skipPartition tokenScanner

        matchRealTable = True
    ElseIf token.tokenType = SqlTokenType.KEY_SELECT Or token.tokenType = SqlTokenType.KEY_WITH Then
        ' 3.2 如果下一个是 token 是 select 或者 with, 则匹配 tableExpr -> select...
        Dim subSelectTableInfo As SqlTranslateSelectTableInfo
        Set subSelectTableInfo = New SqlTranslateSelectTableInfo
        subSelectTableInfo.Init parseSelect(tokenScanner, nestCountBackup, False)

        Set result = subSelectTableInfo
    End If

    ' 4. 退出嵌套的括号
    '    到这里有以下几种可能
    ' 4.1 sql 片段的语法正确，前面已经获取到了 tableExpr
    ' 4.1.1 开始时有 [(], 则在这里需要退出括号嵌套，直到 = nestCountBackup
    ' 4.1.2 开始时没有 [(]，这里就可以直接退出了
    ' 4.2 sql 片段语法不正确，前面没有得到 tableExpr
    ' 4.2.1 开始时有 [(], 则在这里需要退出括号嵌套。退出过程中发现的 [)] 以外的内容全部忽略
    ' 4.1.2 开始时没有 [(]，这里就可以直接退出了
    If nestCountBackup > parenthesisNestCount Then
        While tokenScanner.hasNext
            If parenthesisNestCount <= nestCountBackup Then
                Goto while2Break
            End If

            Set token = tokenScanner.peek
            If token.tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
                parenthesisNestCount = parenthesisNestCount - 1
            End If

            tokenScanner.toNext
        Wend
while2Break:
    End If

    If nestCountBackup = parenthesisNestCount Then
        ' 5. 匹配表别名（如果有的话）
        If matchAliasName(tokenScanner) Then
            Set token = tokenScanner.getNext
            result.setAliasNameToken token
        End If

        ' 6. 如果这一次匹配到的是真实表，则尝试匹配: index_hint_list
        If matchRealTable Then
            parseIndexHintList result, tokenScanner
        End If
    End If

    Set parseTableFactor = result
End Function

' ======================================================
' 解析 join
' ======================================================
' 解析 join
' 需要匹配的内容
'    [[left[outer]|right[outer]|inner]join tableInfo aliasName [condition]]
'    aliasName -> name
'               | as name
'               | E
Private Sub parseJoinFactor(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 如果当前 token 不是以下几种 token，则立刻停止
    '    left / right / inner / join
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If Not SqlHelper.tokenTypeIsJoinStartSqlKey(token.tokenType) Then
        Exit Sub
    End If

    ' 3. 定位到 join 的下一个 token
    If token.tokenType = SqlTokenType.KEY_LEFT Or token.tokenType = SqlTokenType.KEY_RIGHT Then
        tokenScanner.toNext

        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.KEY_OUTER Then
            tokenScanner.toNext
            Set token = tokenScanner.peek
        End If
    ElseIf token.tokenType = SqlTokenType.KEY_INNER Then
        tokenScanner.toNext
        Set token = tokenScanner.peek
    ElseIf token.tokenType = SqlTokenType.KEY_CROSS Then
        tokenScanner.toNext
        Set token = tokenScanner.peek
    End If

    ' 如果无法定位到 join，则立刻停止
    If token.tokenType = SqlTokenType.KEY_JOIN Then
        tokenScanner.toNext
    Else
        Exit Sub
    End If

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 4. 匹配表定义
    Dim tableInfo As ISqlTranslateTableInfo
    Set tableInfo = parseTableFactor(tokenScanner)
    If tableInfo Is Nothing Then
        Exit Sub
    End If

    Dim result As SqlTranslateTableEnv
    Set result = New SqlTranslateTableEnv
    result.Init tableInfo

    ' 5. 搜索条件中使用的列

    ' 跳过开头的 on 关键字
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.KEY_ON Then
        tokenScanner.toNext
    End If

    parseConditionColInfos result, tokenScanner

    ' 保存结果
    env.addTableEnv result
End Sub

' ======================================================
' 解析条件中的非筛选项列
' ======================================================
Private Sub parseConditionColInfos(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 0. 遍历token
    ' 1. 如果是 [;], 则立刻结束
    ' 2. 如果是 运算符, NOT, BINARY 扫描器向后移动，回到 0 继续
    ' 3. 如果是 [(]
    '         扫描器向后移动，回到 0 继续
    ' 4. 如果是 [)]
    '        当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
    '            发生这种情况时，有可能出现了语法错误
    '            此时 token 扫描器不再向前推进，因为已经打破了括号作用域的嵌套数量，这里无法处理
    '        否则减少嵌套数量
    ' 5. 如果是[逻辑运算符]，则跳过
    ' 6. 如果是[算数运算符]，则跳过
    ' 7. 如果[条件运算符]
    '      如果是 in，则尝试匹配 in 运算符的参数
    '      如果是 Exists，则跳过，然后尝试匹配一个[子查询]，然后回到 0 继续
    '      其他则跳过
    '          between，也跳过
    ' 8. 如果是 select 或者 with , 则尝试匹配一个[子查询]，然后回到 0 继续
    ' 9. 如果是其他的 sql 关键字，则结束遍历
    ' 10. 尝试获取一个列

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 0. 遍历token
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    Dim tempEnv As ISqlTranslateEnv
    Dim tempColInfo As ISqlTranslateColInfo
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        If tokenType = SqlTokenType.SEMICOLON Then
            ' 1. 如果是 [;], 则立刻结束
            Goto while1Break
        ElseIf tokenType = SqlTokenType.KEY_NOT Or tokenType = SqlTokenType.KEY_BINARY Then
            ' 2. 如果是 运算符, NOT, BINARY 扫描器向后移动，回到 0 继续
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.LEFT_PARENTHESIS Then
            ' 3. 如果是 [(]
            '         扫描器向后移动，回到 0 继续
            parenthesisNestCount = parenthesisNestCount + 1
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            ' 4. 如果是 [)]
            '    当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
            '        发生这种情况时，有可能出现了语法错误
            '        此时 token 扫描器不再向前推进，因为已经打破了括号作用域的嵌套数量，这里无法处理
            '    否则减少嵌套数量

            If parenthesisNestCount <= nestCountBackup Then
                ' token 扫描器不能向前推进，因为已经打破了括号作用域的嵌套数量，这里无法处理
                Goto while1Break
            Else
                parenthesisNestCount = parenthesisNestCount - 1
                tokenScanner.toNext
            End If
        ElseIf SqlHelper.tokenTypeIsLogicOperator(tokenType) Then
            ' 5. 如果是[逻辑运算符]，则跳过
            tokenScanner.toNext
        ElseIf SqlHelper.tokenTypeIsComputeOperator(tokenType) Then
            ' 6. 如果是[算数运算符]，则跳过
            tokenScanner.toNext
        ElseIf SqlHelper.tokenTypeIsConditionOperator(tokenType) Then
            ' 7. 如果[条件运算符]
            If tokenType = SqlTokenType.KEY_IN Then
                ' 7.1 如果是 in，则尝试匹配 in 运算符的参数
                addConditionColInfoToEnv env, parseCompareInParam(tokenScanner)
            ElseIf tokenType = SqlTokenType.KEY_EXISTS Then
                ' 7.2 如果是 Exists，则跳过，然后尝试匹配一个[子查询]，然后回到 0 继续
                tokenScanner.toNext
                Set tempEnv = parseSelect(tokenScanner, nestCountBackup, False)
                env.addSubEnv tempEnv
            Else
                ' 7.3 其他情况全部跳过
                '     between，也跳过
                tokenScanner.toNext
            End If
        ElseIf tokenType = SqlTokenType.KEY_SELECT Or tokenType = SqlTokenType.KEY_WITH Then
            ' 8. 如果是 select 或者 with , 则尝试匹配一个[子查询]，然后回到 0 继续
            Set tempEnv = parseSelect(tokenScanner, nestCountBackup, False)
            env.addSubEnv tempEnv
        ElseIf SqlHelper.isSqlKeyTokenType(tokenType) Then
            ' 9. 如果是其他的 sql 关键字，则结束遍历
            Goto while1Break
        Else
            ' 10. 尝试获取一个列
            Set tempColInfo = parseColExpr(tokenScanner)

            If tempColInfo Is Nothing Then
                ' 如果这次匹配过程中没有匹配到任何列对象，则说明发生了语法异常
                ' 在这里手动向前推进扫描器，以兼容语法异常
                tokenScanner.toNext
            Else
                env.addConditionColInfo tempColInfo
            End If
        End If
    Wend
while1Break:
End Sub

' 将数组引用中的列对象添加到作用域中
Private Sub addConditionColInfoToEnv(ByRef env As ISqlTranslateEnv, ByRef colInfos As IObjArrayRef)
    If colInfos Is Nothing Then
        Exit Sub
    End If

    Dim i As Long
    Dim c As ISqlTranslateColInfo

    With colInfos
        For i = 0 To .MaxIndex
            Set c = .Item(i)

            env.addConditionColInfo c
        Next
    End With
End Sub

' ======================================================
' 解析 where 中的列定义
' ======================================================
Private Sub parseWhereColInfos(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 如果 token 不是 where，则立刻停止
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_WHERE Then
        Exit Sub
    End If
    tokenScanner.toNext

    ' 3. 解析条件中列
    parseConditionColInfos env, tokenScanner
End Sub

' ======================================================
' 解析非标准 sql 内容
' ======================================================
' 解析非标准 sql
Private Function parseAbnormalSnippets(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateEnv
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 1. 记录括号嵌套深度: [起始括号嵌套深度 = nestCountBackup]
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 2. 创建一个[活动作用域]
    Dim activeEnv As SqlTranslateSnippetsEnv
    Set activeEnv = New SqlTranslateSnippetsEnv
    activeEnv.Init SqlTokenType.KEY_SELECT

    ' 3. 收集子级作用域的内容 + 当前作用域内条件、分组、排序等位置上的列
    Dim scannerIndex As Long
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        scannerIndex = tokenScanner.ActiveIndex

        ' 3.1 遍历 token
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        If tokenType = SqlTokenType.SEMICOLON Then
            ' 3.2 如果是 [;], 说明当前 sql 片段已经结束，立刻结束遍历
            GoTo matchTokenBreak
        ElseIf tokenType = SqlTokenType.LEFT_PARENTHESIS Then
            ' 3.3 如果是 [(]
            ' 3.3.1 增加嵌套数量
            parenthesisNestCount = parenthesisNestCount + 1
            ' 3.3.2 然后扫描器向后移动
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            ' 3.4 如果是 [)], 检查

            If parenthesisNestCount <= nestCountBackup Then
                ' 3.4.1 如果: 当前的括号嵌套数量 <= [起始括号嵌套深度 = nestCountBackup] ，立刻结束遍历
                '     - 说明已经碰到了这个片段的边界
                GoTo matchTokenBreak
            Else
                ' 3.4.2 否则
                ' 3.4.2.1. 括号嵌套深度 - 1
                parenthesisNestCount = parenthesisNestCount - 1
                ' 3.4.2.2. 然后跳过这个 token
                tokenScanner.toNext
            End If
        ElseIf tokenType = SqlTokenType.KEY_FROM Then
            ' 3.5 如果是 from 开始的，则匹配 From 作用域
            ' from
            parseFrom activeEnv, tokenScanner
        ElseIf SqlHelper.tokenTypeIsJoinStartSqlKey(tokenType) Then
            ' 3.6 如果是 left / right / inner / join 开始，则尝试去匹配 join 作用域
            parseJoinFactor activeEnv, tokenScanner
        ElseIf tokenType = SqlTokenType.KEY_WHERE Then
            ' 3.7 如果是 where，则尝试匹配 where 后条件中的筛选列，完成后回到 1 继续
            parseWhereColInfos activeEnv, tokenScanner
        ElseIf tokenType = SqlTokenType.KEY_GROUP_BY Then
            ' 3.8 如果是 GROUP BY，匹配内部的列，完成后回到 1 继续
            parseGroupByColInfos activeEnv, tokenScanner
        ElseIf tokenType = SqlTokenType.KEY_HAVING Then
            ' 3.9 如果是 HAVING，匹配内部的列，完成后回到 1 继续
            parseHavingColInfos activeEnv, tokenScanner
        ElseIf tokenType = SqlTokenType.KEY_ORDER_BY Then
            ' 3.10 如果是 ORDER BY，匹配内部的列，完成后回到 1 继续
            parseOrderByColInfos activeEnv, tokenScanner
        ElseIf tokenType = SqlTokenType.KEY_LIMIT Then
            ' 3.11 如果是 LIMIT，则跳过相关的内容，完成后回到 1 继续
            skipLimit tokenScanner
        Else
            ' 3.12 如果是其他内容，则按照非标准条件列来处理
            parseOtherColInfos activeEnv, tokenScanner
        End If

        ' 3.14 每次遍历后，检查
        '     - 如果这次处理中扫描器没有移动，则说明可能出现了语法问题，所以需要手动推进扫描器
        If scannerIndex = tokenScanner.ActiveIndex Then
            tokenScanner.toNext
        End If
    Wend
matchTokenBreak:

    ' 4. 结束，并返回 [活动作用域 = activeEnv]
    Set parseAbnormalSnippets = activeEnv
End Function

Private Sub parseOtherColInfos(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 清除开始部分中的 sql 关键字
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 如果发现了 [;]，则立刻结束
        If tokenType = SqlTokenType.SEMICOLON Then
            Exit Sub
        End If

        ' 如果开头是 sql 关键字，则直接跳过，直到发现一个非关键字
        If Not SqlHelper.isSqlKeyTokenType(token.tokenType) Then
            Goto while1Break
        End If

        tokenScanner.toNext
    Wend
while1Break:

    ' 3. 解析条件中列
    parseConditionColInfos env, tokenScanner
End Sub

' ======================================================
' 解析 limit
' ======================================================
' 解析 limit
' 需要匹配的内容
'     LIMIT {[offset,] row_count | row_count OFFSET offset}
Private Sub skipLimit(ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 跳过 LIMIT
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_LIMIT Then
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 3. 尝试跳过一个数字
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.STR_NUM Then
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 4. 检查下一个判断是哪种模式
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.COMMA Then
        ' 匹配: LIMIT offset, row_count
        tokenScanner.toNext
        If Not tokenScanner.hasNext Then
            Exit Sub
        End If

        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.STR_NUM Then
            tokenScanner.toNext
        End if
    ElseIf token.tokenType = SqlTokenType.KEY_OFFSET Then
        ' 匹配: LIMIT row_count OFFSET offset
        tokenScanner.toNext
        If Not tokenScanner.hasNext Then
            Exit Sub
        End If

        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.STR_NUM Then
            tokenScanner.toNext
        End If
    End If
End Sub

' ======================================================
' 解析 having 中的列定义
' ======================================================
' 解析 having 中的列定义
' 需要匹配的内容
'     HAVING where_condition
Private Sub parseHavingColInfos(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 过滤开始的 having
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_HAVING Then
        Exit Sub
    End If
    tokenScanner.toNext

    ' 3. 解析条件中列
    parseConditionColInfos env, tokenScanner
End Sub

' ======================================================
' 解析 order by 中的列定义
' ======================================================
' 解析 order by 中的列
' 需要匹配的内容
'     ORDER BY {col_name | expr | position}
'      [ASC | DESC], ...
Private Sub parseOrderByColInfos(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 过滤开始的 group by
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_ORDER_BY Then
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 解析后面的列
    Dim tokenType As SqlTokenType
    Dim tempColInfo As ISqlTranslateColInfo
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 如果发现了一个 [;]，则立刻停止
        If tokenType = SqlTokenType.SEMICOLON Then
            Exit Sub
        End If

        ' 如果是各部分的开始 key，则立刻终止
        If SqlHelper.tokenTypeIsSectionStartSqlKey(tokenType) Then
            Exit Sub
        End If

        ' 尝试解析一个列
        Set tempColInfo = parseMixedColExpr(tokenScanner)
        env.addConditionColInfo tempColInfo

        If Not tokenScanner.hasNext Then
            Exit Sub
        End If

        ' 跳过后面的: ASC | DESC
        Set token = tokenScanner.peek
        tokenType = token.tokenType
        If tokenType = SqlTokenType.KEY_ASC Then
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.KEY_DESC Then
            tokenScanner.toNext
        End If

        If Not tokenScanner.hasNext Then
            Exit Sub
        End If

        ' 如果下一个 token 是 [,]，则继续，否则停止匹配
        Set token = tokenScanner.peek()
        tokenType = token.tokenType
        If tokenType = SqlTokenType.COMMA Then
            tokenScanner.toNext
        Else
            Goto while1Break
        End if
    Wend
while1Break:
End Sub

' ======================================================
' 解析 group by 中的列定义
' ======================================================
' 解析 group by 中的列
' 需要匹配的内容
'     GROUP BY {col_name | expr | position}
'     [ASC | DESC], ... [WITH ROLLUP]
Private Sub parseGroupByColInfos(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 过滤开始的 group by
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_GROUP_BY Then
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 解析后面的列
    Dim tokenType As SqlTokenType
    Dim tempColInfo As ISqlTranslateColInfo
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 如果发现了一个 [;]，则立刻停止
        If tokenType = SqlTokenType.SEMICOLON Then
            Exit Sub
        End If

        ' 如果是各部分的开始 key，则立刻终止
        If SqlHelper.tokenTypeIsSectionStartSqlKey(tokenType) Then
            Exit Sub
        End If

        ' 尝试解析一个列
        Set tempColInfo = parseMixedColExpr(tokenScanner)
        env.addConditionColInfo tempColInfo

        If Not tokenScanner.hasNext Then
            Exit Sub
        End If

        ' 跳过后面的: ASC | DESC
        Set token = tokenScanner.peek
        tokenType = token.tokenType
        If tokenType = SqlTokenType.KEY_ASC Then
            tokenScanner.toNext
        ElseIf tokenType = SqlTokenType.KEY_DESC Then
            tokenScanner.toNext
        End If

        If Not tokenScanner.hasNext Then
            Exit Sub
        End If

        ' 如果下一个 token 是 [,]，则继续，否则停止匹配
        Set token = tokenScanner.peek
        tokenType = token.tokenType
        If tokenType = SqlTokenType.COMMA Then
            tokenScanner.toNext
        Else
            Goto while1Break
        End If
    Wend
while1Break:

    ' 跳过最后的: WITH ROLLUP
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_WITH Then
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_ROLLUP Then
        tokenScanner.back
    Else
        tokenScanner.toNext
    End If
End Sub

' ======================================================
' 解析 partition
' ======================================================
' 跳过 partition 部分
' 需要匹配的内容
'   partition -> [(]partition_name [, partition_name] ...[)]
'   partition_name -> str_name
Private Sub skipPartition(ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 检查第一个 token 是不是 partition
    '    如果不是，则立刻结束
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_PARTITION Then
        Exit Sub
    End If
    tokenScanner.toNext

    ' 3. 消耗连续出现的 [(]

    ' 记录: 起始部分的 [(] 数量
    Dim startLeftParenthesisCount As Integer
    startLeftParenthesisCount = parenthesisNestCount
    consumeLeftParenthesis tokenScanner
    startLeftParenthesisCount = parenthesisNestCount - startLeftParenthesisCount

    ' 如果 partition 后面没有 ()，则发生语法异常，立刻结束
    If startLeftParenthesisCount = 0 Then
        Exit Sub
    End If

    ' 4. 开始匹配名称部分
    '    这里只能出现两种 token: str_name + [,]
    '    如果出现了其他类型的 token，则停止遍历

    ' 4.1 尝试匹配第一个 partition_name
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.STR_NAME Then
        tokenScanner.toNext
    End If
    ' 为了兼容部分异常，即使第一个不是 name，也不立刻结束

    ' 4.2 匹配剩余的所有 [, partition_name]
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        ' 4.2.1 如果是 [,]，则跳过，继续处理下一个 token
        '       这里为了兼容异常，尝试跳过多个连续出现的 [,]
        If token.tokenType = SqlTokenType.COMMA Then
            Do
                tokenScanner.toNext

                If Not tokenScanner.hasNext Then
                    GoTo partitionBreak
                End If

                Set token = tokenScanner.peek
            Loop While token.tokenType = SqlTokenType.COMMA
        Else
            ' 如果开头不是 [,]，则立刻结束遍历
            Goto partitionBreak
        End If

        ' 4.2.2 如果是 name，则跳过，继续处理下一组: [, partition_name]
        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.STR_NAME Then
            tokenScanner.toNext
        Else
            ' 有 [,] 没有 name，可能是发生了语法错误，这里不需要处理
            ' 也不需要归还 [,]
            Goto partitionBreak
        End If
    Wend
partitionBreak:

    ' 5. 退出可能会在末尾出现的 [)]
    '    这里无法再完全兼容语法错误，如果遇到了 [)] 以外的内容，则立刻结束
    If startLeftParenthesisCount > 0 Then
        While tokenScanner.hasNext
            If startLeftParenthesisCount = 0 Then
                GoTo endParenthesisBreak
            End If

            Set token = tokenScanner.peek
            If token.tokenType <> SqlTokenType.RIGHT_PARENTHESIS Then
                GoTo endParenthesisBreak
            End If

            tokenScanner.toNext
            startLeftParenthesisCount = startLeftParenthesisCount - 1
            parenthesisNestCount = parenthesisNestCount - 1
        Wend
endParenthesisBreak:
    End If
End Sub

' ======================================================
' 解析 index_hint_list
' ======================================================
' 匹配 index hint list
' 参考: https://dev.mysql.com/doc/refman/5.7/en/join.html
' 需要匹配的内容
' index_hint_list -> index_hint [, index_hint] ...
Private Sub parseIndexHintList(ByRef tableInfo As ISqlTranslateTableInfo, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2.匹配第一个 index_hint
    Dim scannerActiveIndex As Long
    scannerActiveIndex = tokenScanner.ActiveIndex

    parseIndexHint tableInfo, tokenScanner

    If scannerActiveIndex = tokenScanner.ActiveIndex Then
        Exit Sub
    End If

    ' 3. 匹配剩余的: [, index_hint] ...
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 3.1 跳过开头的 [,]
        '     为了兼容语法异常，这里跳过连续出现的 [,]
        If tokenType = SqlTokenType.COMMA Then
            tokenScanner.toNext
outCommaBreak:
        Else
            GoTo matchTokenBreak
        End If

        If Not tokenScanner.hasNext Then
            GoTo matchTokenBreak
        End If

        ' 3.2 匹配 index_hint
        '     如果扫描器的活动索引没有发生变化，则立刻结束
        scannerActiveIndex = tokenScanner.ActiveIndex
        parseIndexHint tableInfo, tokenScanner

        If scannerActiveIndex = tokenScanner.ActiveIndex Then
            ' 结束之前需要归前一个 [,]
            ' 因为 index_hint_list 是 table_reference 的最后一部分
            ' 如果不归还，可能会吞掉公式: [table_reference, table_reference] 中的 [,]
            tokenScanner.back
            GoTo matchTokenBreak
        End If
    Wend
matchTokenBreak:

End Sub


' 匹配
'  index_hint ->            USE {INDEX|KEY} [FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
'              | {IGNORE|FORCE} {INDEX|KEY} [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
Private Sub parseIndexHint(ByRef tableInfo As ISqlTranslateTableInfo, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 检查开头的 token
    ' 如果是 USE, ignore 或者 force，则跳过，否则立刻停止
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    Set token = tokenScanner.peek
    tokenType = token.tokenType

    If tokenType = SqlTokenType.KEY_USE Then
        tokenScanner.toNext
    ElseIf tokenType = SqlTokenType.KEY_IGNORE Then
        tokenScanner.toNext
    ElseIf tokenType = SqlTokenType.KEY_FORCE Then
        tokenScanner.toNext
    Else
        Exit Sub
    End If

    ' 3. 跳过 INDEX|KEY
    Set token = tokenScanner.peek
    tokenType = token.tokenType
    If tokenType <> SqlTokenType.KEY_INDEX And tokenType <> SqlTokenType.KEY_KEY Then
        Exit Sub
    End If
    tokenScanner.toNext

    ' 4. 尝试跳过: [FOR {JOIN|ORDER BY|GROUP BY}]
    Set token = tokenScanner.peek
    tokenType = token.tokenType
    ' 4.1 如果不是 For 开头，则跳过
    If tokenType = SqlTokenType.KEY_FOR Then
        tokenScanner.toNext

        ' 4.2 跳过后面的: JOIN|ORDER BY|GROUP BY
        '     如果没有符合规则的关键字，则立刻结束（这里比较复杂，无法做语法异常兼容）
        If tokenType <> SqlTokenType.KEY_JOIN _
            And tokenType <> SqlTokenType.KEY_ORDER_BY _
            And tokenType <> SqlTokenType.KEY_GROUP_BY Then
                Exit Sub
        End If

        tokenScanner.toNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType
    End If

    ' 5. 跳过 index_list
    ' 5.1 跳过开头的 [(]
    Dim leftParenthesisCount As Integer
    leftParenthesisCount = parenthesisNestCount
    consumeLeftParenthesis tokenScanner
    leftParenthesisCount = parenthesisNestCount - leftParenthesisCount

    ' 如果没有匹配到任何 [(]，则立刻结束
    ' 这里可能发生了语法异常
    If leftParenthesisCount = 0 Then
        Exit Sub
    End If

    ' 5.2 匹配 index_list
    parseIndexList tableInfo, tokenScanner

    ' 5.3 退出 [)]
    exitRightParenthesis tokenScanner, (parenthesisNestCount - leftParenthesisCount)
End Sub

' 匹配
'    index_list -> index_name [, index_name] ...
Private Sub parseIndexList(ByRef tableInfo As ISqlTranslateTableInfo, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 匹配开头的 index_name
    Dim token As SqlToken
    Set token = tokenScanner.peek()
    If token.tokenType = SqlTokenType.STR_NAME Then
        tokenScanner.toNext
    End If
    ' 这里不立刻终止，以兼容语法异常，如: [,,, index_name]

    ' 3. 匹配剩余的: [, index_name] ...
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        ' 3.1 跳过开头的 [,]
        '     为了兼容语法异常，这里跳过连续出现的 [,]
        If token.tokenType = SqlTokenType.COMMA Then
            Do
                tokenScanner.toNext

                If Not tokenScanner.hasNext Then
                    GoTo matchTokenBreak
                End If

                Set token = tokenScanner.peek
            Loop While token.tokenType = SqlTokenType.COMMA
        Else
            GoTo matchTokenBreak
        End If

        ' 3.2 匹配 index_name
        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.STR_NAME Then
            tokenScanner.toNext
        Else
            GoTo matchTokenBreak
        End If
    Wend
matchTokenBreak:
End Sub

' ======================================================
' 解析select筛选列定义
' ======================================================
' 处理 select 筛选项
' 匹配的内容:
' selectColInfo -> mixedColExpr aliasName
'     aliasName -> name
'                | as name
'                | E
'
' 结束时当前匹配时，一定会定位到一个最近的结束标记
Private Function parseSelectColInfo(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateColInfo
    ' 1. 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 2. 尝试匹配一个列定义
    Dim result As ISqlTranslateColInfo
    Set result = parseMixedColExpr(tokenScanner)
    If result Is Nothing Then
        ' return null
        Exit Function
    End If

    ' 3. 如果片段已经结束了，则立刻结束
    If Not tokenScanner.hasNext Then
        Set parseSelectColInfo = result
        Exit Function
    End If

    ' 4. 尝试匹配别名
    ' 4.1 匹配失败，则返回当前结果
    If Not matchAliasName(tokenScanner) Then
        Set parseSelectColInfo = result
        Exit Function
    End If

    ' 4.2 保存别名
    Dim aliasToken As SqlToken
    Set aliasToken = tokenScanner.getNext
    result.setAliasNameToken aliasToken

    Set parseSelectColInfo = result
End Function

' 匹配筛选列表达式
' 需要匹配的内容:
'      mixedColExpr -> logicExpr
'                     | computeColExpr
'
'          逻辑表达式
'          logicExpr -> (logicExpr)
'                     | logicExpr logicOp wrappedCompareExpr | wrappedCompareExpr
'          逻辑表达式转换
'          logicExpr -> (logicExpr)
'                     | wrappedCompareExpr R
'                  R -> logicOp logicExpr R | wrappedCompareExpr
'
'          比较表达式
' wrappedCompareExpr -> (wrappedCompareExpr)
'                     | compareExpr | {NOT | BINARY} compareExpr
'        compareExpr -> (compareExpr)
'                     | computeColExpr conditionOp computeColExpr
'
'          算数表达式
'     computeColExpr -> (computeColExpr)
'                     | computeColExpr computeOp {colExpr|logicExpr} | {colExpr|logicExpr}
'          算数表达式转换
'     computeColExpr -> {colExpr|logicExpr} R
'                  R -> computeOp computeColExpr R | E
'
' 各类型的表达式需要的运算符
'    逻辑运算符  logicOp  : and or
'    条件运算符  conditionOp:
'                  二元比较 : < > <= >= != <> = is like
'                  三元比较 : elem0 between elem1 and elem2
'                             elem1 + elem2 是 mixedColExpr
'                              这里的 mixedColExpr，在无括号的情况下，不能出现逻辑运算符
'                  多元比较 : elem0 in (elem1,.., elemN)
'                             elem1,.., elemN 可以是 mixedColExpr
'                             包含逻辑表达式的情况: true in ('a'='a' and 'b'='b', false)
'                             in 后面必须跟随括号，否则按照错误语法处理
'                  一元比较 : Exists (select....)
'                             比较内容必须是一个子查询
'    算数运算符  computeOp: + - * / ||
'
' 符号优先级（不考虑括号嵌套）
'     算数运算符 > 条件运算符 > 逻辑运算符
'
' 停止条件
'   1. [)] 的嵌套深度和初始深度不一致
'   2. 遇到了[;]
'   3. 无法在一个列后面搜索到某种运算符
'
' 搜索方式
'   这里只需要获取一个完成的列对象(单例对象 或 复合式列对象)
'   所以不去严格区分各个子表达式的类型，除了各个运算符对运算内容有要求
'   其他情况下只要一个列后面是运算符就继续执行匹配，直到遇到了停止条件
'
' 特殊情况 1
'   对于任何筛选项中的列，除了 `Exists` 运算，第一次发现的运算一定不能是逻辑运算符
'   因为只有比较运算的结果才能在后面附加逻辑运算符
'   如果发现了一个逻辑运算符，则说明当前可能是一个非标准 sql，需要右其他方法处理
'       如: select a and b and c  ---> 发现 a 后应该停止，and b and c 由条件匹配器去处理
'   Exists 比较特殊，它自己就能形成一个比较运算结果
'
' @param tokenScanner token 扫描器
' @param needStopInFirstLogic 是否需要在第一个逻辑表达式完成时结束
'        如 select a = 'x' And b = 'y'
'              如果 needStopInFirstLogic = true，将会在 a = 'x' 后立刻停止
'              如果 needStopInFirstLogic = false，将会完整的扫描、处理: a = 'x' And b = 'y'
'        对于多层嵌套的情况，将会在退出嵌套后的第一个逻辑操作符处停下来
'          如: select 'x' || (a > b And c > d) || c = 'zzz' And b='y'
'              这将会在 c = 'zzz' And 处停下
Private Function parseMixedColExpr(ByRef tokenScanner As ObjArrayRefScanner, Optional ByVal needStopInFirstLogic As Boolean = False) As ISqlTranslateColInfo
    ' 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Set parseMixedColExpr = Nothing
        Exit Function
    End If

    ' 0. 跳过开头连续的 [(] 和 {NOT | BINARY}
    ' 1. 尝试匹配一个 colExpr
    ' 1.1 检查第一个 token 是不是 Exists,
    '         如果是，则扫描器向后推进，然后尝试匹配一个 [子查询]
    ' 1.2 如果第一个 token 不是 Exists，则尝试匹配一个筛选列表达式
    ' 2. 检查下一个是不是: [条件运算符]或者[算数运算符]。如果不是则需要立刻退出
    '      如果是 1.1 跳转的，则不需要做这个检查
    '      如果是 1.2 跳转的，则必须做这个检查
    '      假设匹配成功，在这里处理【需要注意的特殊情况1】
    '      假设匹配失败，不立刻停止，为了能够兼容非标准片段，向后看一个 token
    '          如果是算数运算符 + 条件运算符，则继续处理，以兼容语法错误
    '              如: || b || c
    '          如果是逻辑运算符，说明现在可能进入了非标准 sql 的【条件部分】，需要立刻停止
    '              如: 一上来就是 and t1.a='xxx' and t1.b ='xxx'
    '
    ' 4. 匹配剩余的表达式
    ' 4.0 遍历 token 进行匹配
    ' 4.1 如果遇到了 [;]，则立刻停止匹配
    ' 4.2 如果遇到了 [)]
    ' 4.2.1 当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
    '            发生这种情况时，有可能出现了语法错误
    '            此时 token 扫描器不再向前推进，因为已经打破了括号作用域的嵌套数量，这里无法处理
    ' 4.2.2 在这里尝试退出连续出现的 [)]
    ' 4.2.2.1 如果 当前的括号嵌套数量 = nestCountBackup ，则退出 4.2.2
    ' 4.2.2.2 否则减少嵌套数量
    ' 4.2.3 退出 [)] 完成后，继续 4.3
    '
    ' 4.3 尝试匹配各类型的运算符
    ' 4.3.1 如果无法匹配一个运算符，则退出 4
    ' 4.3.2 如果是 [算数运算符] 或者 [逻辑运算符]，则继续执行 4.4
    ' 4.3.3 如果是条件运算符
    ' 4.3.4.1 如果是 Exists，则尝试匹配一个[子查询], 然后回到 4.0 继续
    ' 4.3.4.2 如果是 between，则进行特殊的匹配，然后回到 4.0 继续
    '             在无括号的情况下，不能出现逻辑运算符
    ' 4.3.4.3 如果是 in，则进行特殊匹配，然后回到 4.0 继续
    '
    ' 4.4 匹配子表达式
    ' 4.4.1 跳过开头连续的 [(] 和 {NOT | BINARY}
    ' 4.4.2 尝试匹配下一个 colExpr
    ' 4.4.2.1 检查下一个 token 是不是 Exists
    '          如果是，则扫描器向后推进，然后尝试匹配一个 [子查询]，然后回到 4.0 继续
    ' 4.4.2.2 如果下一个 token 不是 Exists，则尝试匹配一个筛选列表达式，然后回到 4.0 继续
    '
    ' 5. 退出嵌套的[)]
    '    如果遇到了 ';'，则直接退出
    '    如果遇到了 当前的括号嵌套数量 <= nestCountBackup，则直接退出
    '    如果遇到了 当前的括号嵌套数量 > nestCountBackup ，则需要将尝试退出 [)]
    '               中间遇到的非 [;],[(],[)] 全部忽略

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    Dim result As ISqlTranslateColInfo
    Set result = Nothing

    ' 0. 跳过开头连续的 [(] 和 {NOT | BINARY}
    Dim token As SqlToken
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.LEFT_PARENTHESIS Then
            parenthesisNestCount = parenthesisNestCount + 1
            tokenScanner.toNext
        ElseIf token.tokenType = SqlTokenType.KEY_NOT Then
            tokenScanner.toNext
        ElseIf token.tokenType = SqlTokenType.KEY_BINARY Then
            tokenScanner.toNext
        Else
            Goto while1Break
        End If
    Wend
while1Break:

    If Not tokenScanner.hasNext Then
        Set parseMixedColExpr = Nothing
        Exit Function
    End If

    ' 1. 尝试匹配一个 colExpr
    Dim tokenType As SqlTokenType
    Set token = tokenScanner.peek
    tokenType = token.tokenType

    If tokenType = SqlTokenType.KEY_EXISTS Then
        ' 1.1 匹配一个子查询作用域，并保证成列对象
        tokenScanner.toNext
        Set result = wrapperSelectEnvToColInfo(parseSelect(tokenScanner, nestCountBackup, False))
    Else
        ' 1.2 第一个 token 不是 Exists，则尝试匹配一个筛选列表达式
        Set result = parseColExpr(tokenScanner)

        ' 1.3 在这里尝试退出连续出现的括号，最小退出到: nestCountBackup
        '     以防止以下情况无法正常解析:
        '     select ((case ... end) + b)
        '     内层的 case 无法主动退出 [)], 如果不在这里主动退出会导致整个列无法正常解析
        exitRightParenthesis tokenScanner, nestCountBackup

        If Not tokenScanner.hasNext Then
            Set parseMixedColExpr = result
            Exit Function
        End If

        ' 2. 检查下一个是不是: [条件运算符]或者[算数运算符]。如果不是则需要立刻退出
        Set token = tokenScanner.peek
        tokenType = token.tokenType
        If Not SqlHelper.tokenTypeIsComputeOperator(tokenType) _
            And Not SqlHelper.tokenTypeIsConditionOperator(tokenType) Then
            Set parseMixedColExpr = result
            Exit Function
        End If
    End If

    ' 4. 匹配剩余的表达式
    ' 4.0 遍历 token 进行匹配
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 4.1 如果遇到了 [;]，则立刻停止匹配
        If tokenType = SqlTokenType.SEMICOLON Then
            Goto while2Break
        End If

        ' 4.2 如果遇到了 [)]
        If tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            ' 4.2.1 当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
            '       发生这种情况时，有可能出现了语法错误
            '       token 扫描器不能向前推进，因为已经打破了括号作用域的嵌套数量，这里无法处理
            If parenthesisNestCount <= nestCountBackup Then
                Goto while2Break
            End If

            ' 4.2.2 在这里尝试退出连续出现的 [)]
            While tokenScanner.hasNext
                Set token = tokenScanner.peek
                tokenType = token.tokenType
                If tokenType <> SqlTokenType.RIGHT_PARENTHESIS Then
                    Goto while3Break
                End If

                parenthesisNestCount = parenthesisNestCount - 1
                tokenScanner.toNext

                If nestCountBackup = parenthesisNestCount Then
                    If tokenScanner.hasNext Then
                        Set token = tokenScanner.peek
                        tokenType = token.tokenType
                    End If

                    Goto while3Break
                End If
            Wend
while3Break:
        End If

        If Not tokenScanner.hasNext Then
            Set parseMixedColExpr = result
            Exit Function
        End If

        ' 4.3 尝试匹配各类型的运算符
        If SqlHelper.tokenTypeIsComputeOperator(tokenType) Then
            ' 4.3.2
            ' 算数运算符
            tokenScanner.toNext
        ElseIf SqlHelper.tokenTypeIsLogicOperator(tokenType) Then
            ' 4.3.2
            ' 逻辑运算符

            If needStopInFirstLogic Then
                ' 如果已经退出了括号的嵌套作用域，则在第一个逻辑运算符处停止
                If parenthesisNestCount <= nestCountBackup Then
                    Goto while2Break
                End If
            End If

            tokenScanner.toNext
        ElseIf SqlHelper.tokenTypeIsConditionOperator(tokenType) Then
            ' 4.3.3
            ' 条件运算符

            If tokenType = SqlTokenType.KEY_BETWEEN Then
                ' 4.3.4.2 如果是 between，则进行特殊的匹配，然后回到 4.0 继续
                '         在无括号的情况下，不能出现逻辑运算符
                Set result = mergeColInfoWithArray(result, parseBetweenParam(tokenScanner))
                Goto while2Continue
            ElseIf tokenType = SqlTokenType.KEY_IN Then
                ' 4.3.4.3 如果是 in，则进行特殊匹配，然后回到 4.0 继续
                Set result = mergeColInfoWithArray(result, parseCompareInParam(tokenScanner))
                Goto while2Continue
            ElseIf tokenType = SqlTokenType.KEY_EXISTS Then
                ' 4.3.4.1 如果是 Exists，则尝试匹配一个[子查询], 然后回到 4.0 继续
                tokenScanner.toNext
                Set result = mergeColInfo(result, wrapperSelectEnvToColInfo(parseSelect(tokenScanner, nestCountBackup, False)))
                Goto while2Continue
            Else
                ' 其他的条件运算符需要主动向后推进扫描器
                tokenScanner.toNext
            End If
        Else
            ' 4.3.1
            ' 如果没有匹配到任何一个运算符，则立刻停止遍历
            Goto while2Break
        End If

        ' 4.4 匹配子表达式
        ' 4.4.1 跳过开头连续的 [(] 和 {NOT | BINARY}
        While tokenScanner.hasNext
            Set token = tokenScanner.peek
            If token.tokenType = SqlTokenType.LEFT_PARENTHESIS Then
                parenthesisNestCount = parenthesisNestCount + 1
                tokenScanner.toNext
            ElseIf token.tokenType = SqlTokenType.KEY_NOT Then
                tokenScanner.toNext
            ElseIf token.tokenType = SqlTokenType.KEY_BINARY Then
                tokenScanner.toNext
            Else
                Goto while4Break
            End If
        Wend
while4Break:

        If Not tokenScanner.hasNext Then
            Set parseMixedColExpr = result
            Exit Function
        End If

        ' 4.4.2 尝试匹配下一个 colExpr
        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.KEY_EXISTS Then
            ' 4.4.2.1 如果下一个 Exists ，则匹配一个子查询作用域，并保存成列对象
            tokenScanner.toNext
            Set result = mergeColInfo(result, wrapperSelectEnvToColInfo(parseSelect(tokenScanner, nestCountBackup, False)))
        Else
            ' 4.4.2.2 如果下一个 token 不是 Exists，则尝试匹配一个筛选列表达式，然后回到 4.0 继续
            Set result = mergeColInfo(result, parseColExpr(tokenScanner))
        End If
while2Continue:
    Wend
while2Break:

    ' 5. 退出嵌套的[)]
    If token.tokenType <> SqlTokenType.SEMICOLON _
        And parenthesisNestCount > nestCountBackup Then
        While tokenScanner.hasNext
            If parenthesisNestCount <= nestCountBackup Then
                Goto while5Break
            End If

            Set token = tokenScanner.peek
            tokenType = token.tokenType

            If tokenType = SqlTokenType.SEMICOLON Then
                ' 如果遇到了 ';'，则直接退出
                Goto while5Break
            ElseIf tokenType = SqlTokenType.LEFT_PARENTHESIS Then
                parenthesisNestCount = parenthesisNestCount + 1
            ElseIf tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
                parenthesisNestCount = parenthesisNestCount - 1
            End If
            ' 中间遇到的非 [;],[(],[)] 全部忽略 ！！！！！

            tokenScanner.toNext
        Wend
while5Break:
    End If

    Set parseMixedColExpr = result
End Function

' 匹配单列名称表达式
' 需要匹配的内容
'       colExpr ->  subColExpr
'                | -subColExpr
'                | +subColExpr
'    subColExpr -> (subColExpr)
'                | colName
'                | number
'                | string
'                | true
'                | false
'                | null
'                | *
'                | function
'                | caseStatement
'                | subSelect
'                | castFunc
' caseStatement -> case [mixedColExpr] when mixedColExpr then mixedColExpr  <- 关键字 case 开头
'                  [when mixedColExpr then mixedColExpr]
'                  [else mixedColExpr]
'                  end [case]
'      castFunc -> cast(mixedColExpr As type)
'      function -> func(funcParams)  <- STR_FUNC 开头
'    funcParams -> mixedColExpr
'                | mixedColExpr,mixedColExpr
'                | *
'                | empty
'     subSelect -> SELECT .... <--- select 开头
'       colName -> name
'                | tableName.name
'    colNameVal -> name | *
'     tableName -> name
'        number -> STR_NUM
'        string -> STR_STR
'
' 特殊情况 1: 多层正负号
'     -((+(-3))) = 3
'     发现正负号的有效条件:
'         未进入过括号 + 已经进入过括号，但是括号嵌套深度比初始值更高
Private Function parseColExpr(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateColInfo
    Dim result As ISqlTranslateColInfo
    Set result = Nothing

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 记录: 是否进入过括号
    Dim hasBeenNest As Boolean
    hasBeenNest = False

    ' 搜索策略，搜索到一个表达式之后，立刻停止
    ' 0. 遍历开始
    ' 1. 如果遇到了 [+] 或 [-]
    '      如果从未进入过括号，则扫描器向后移动
    '      如果已经进入过括号，但是当前括号嵌套深度 > nestCountBackup, 则扫描器向后移动
    '      否则，立刻终止遍历，扫描器不移动（说明碰到了一个运算符）
    '
    ' 2. 如果是一个左括号 [(]
    '      增加嵌套数量 + 记录当前已经进入过一个括号作用域
    '      然后扫描器向后移动
    ' 3. 如果是一个右括号 [)]
    '        当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
    '            发生这种情况时，有可能出现了语法错误
    '            此时 token 扫描器不再向前推进，因为已经打破了括号作用域的嵌套数量，这里无法处理
    '        否则减少嵌套数量
    ' 4. 如果是一个 STR_FUNC ，去匹配一个 function
    ' 5. 如果发现了一个 STR_NUM，生成字面量列，然后扫描器向后移动
    ' 6. 如果发现了一个 STR_STR，生成字面量列，然后扫描器向后移动
    ' 7. 如果发现了一个 TRUE，生成字面量列，然后扫描器向后移动
    ' 8. 如果发现了一个 FALSE，生成字面量列，然后扫描器向后移动
    ' 9. 如果发现了一个 NULL，生成字面量列，然后扫描器向后移动
    ' 10. 如果发现了一个 sql 常量，生成字面量列，然后扫描器向后移动
    ' 11. 如果发现了一个 *，生成全数据列，然后扫描器向后移动
    ' 12. 如果发现了一个 case 关键字，尝试匹配 case
    ' 13. 如果发现了一个 cast 关键字，尝试匹配 cast
    ' 14. 如果发现了一个 select 或者 with ，则尝试匹配一个子查询
    '      假设正在处理的这个 sql 片段的局部的语法是正确的，那么:
    '          在处理子查询前，嵌套深度可能会是 N,
    '          等到子查询处理完成后，嵌套深度有可能会降低到 N - X，但是也绝不会比 nestCountBackup 更少
    '      select 检查停止的条件是: 遇到了多余的[)], 片段已经停止，遇到了[;]
    '          所以是否需要停止下一次一定能够发现
    ' 15. 如果发现了一个 STR_NAME，尝试匹配一个列名
    ' 16. 如果是【筛选项中的无意义关键字】，则跳过，扫描器向后移动
    '     然后直接回到 0 继续，不做 17
    ' 17. 其他情况: 终止遍历
    '
    ' 18. 每次遍历结束前的检查
    '    如果: 当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
    '    否则，回到 0 继续

    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        If tokenType = SqlTokenType.OPERA_MINUS _
            Or tokenType = SqlTokenType.OPERA_PLUS Then
            If Not hasBeenNest Then
                tokenScanner.toNext
            ElseIf hasBeenNest And parenthesisNestCount > nestCountBackup Then
                tokenScanner.toNext
            Else
                Goto matchTokenBreak
            End If
        Else
            Select Case tokenType
                Case SqlTokenType.LEFT_PARENTHESIS
                    hasBeenNest = true
                    parenthesisNestCount = parenthesisNestCount + 1
                    tokenScanner.toNext
                Case SqlTokenType.RIGHT_PARENTHESIS
                    If parenthesisNestCount <= nestCountBackup Then
                        ' token 扫描器不能向前推进，因为已经打破了括号作用域的嵌套数量，这里无法处理
                        Goto matchTokenBreak
                    Else
                        parenthesisNestCount = parenthesisNestCount - 1
                        tokenScanner.toNext
                    End If
                Case SqlTokenType.STR_FUNC, SqlTokenType.KEY_VALUES
                    ' 这里可以出现关键字 values，但是这里的函数 values
                    ' 分词时无法区分，只能在这里做特殊处理
                    ' 这里和 func 做相同的处理
                    Set result = mergeColInfo(result, parseFunction(tokenScanner))
                Case SqlTokenType.STR_NUM
                    Set result = mergeColInfo(result, createLiteralColInfo(token))
                    tokenScanner.toNext
                Case SqlTokenType.STR_STR
                    Set result = mergeColInfo(result, createLiteralColInfo(token))
                    tokenScanner.toNext
                Case SqlTokenType.KEY_TRUE
                    Set result = mergeColInfo(result, createLiteralColInfo(token))
                    tokenScanner.toNext
                Case SqlTokenType.KEY_FALSE
                    Set result = mergeColInfo(result, createLiteralColInfo(token))
                    tokenScanner.toNext
                Case SqlTokenType.KEY_NULL
                    Set result = mergeColInfo(result, createLiteralColInfo(token))
                    tokenScanner.toNext
                Case SqlTokenType.ASTERISK
                    Set result = mergeColInfo(result, createAsteriskColInfo(Nothing, Nothing, token))
                    tokenScanner.toNext
                Case SqlTokenType.KEY_CASE
                    Set result = mergeColInfo(result, parseCaseStatement(tokenScanner))
                Case SqlTokenType.KEY_CAST
                    Set result = mergeColInfo(result, parseCastFunc(tokenScanner))
                Case SqlTokenType.KEY_SELECT, SqlTokenType.KEY_WITH
                    Set result = mergeColInfo( _
                                result, _
                                wrapperSelectEnvToColInfo(parseSelect(tokenScanner, nestCountBackup, True)) _
                            )
                Case SqlTokenType.STR_NAME
                    Set result = mergeColInfo(result, parseColName(tokenScanner))
                Case Else
                    If SqlHelper.tokenTypeIsInSelectStartActionSqlKey(tokenType) Then
                        tokenScanner.toNext
                        Goto matchTokenContinue
                    ElseIf SqlHelper.tokenTypeIsSqlConst(tokenType) Then
                        ' 如果是 sql 常量，则创建一个字面量列
                        Set result = mergeColInfo(result, createLiteralColInfo(token))
                        tokenScanner.toNext
                    Else
                        Goto matchTokenBreak
                    End If
            End Select

            ' 每次遍历结束前的检查
            '    如果: 当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
            If parenthesisNestCount <= nestCountBackup Then
                Goto matchTokenBreak
            End If
        End If
matchTokenContinue:
    Wend
matchTokenBreak:

    Set parseColExpr = result
End Function

' 处理 cast 函数
' 需要匹配的内容:
'     castFunc -> cast(mixedColExpr As type)
Private Function parseCastFunc(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateColInfo
    ' 1. 如果 token 扫描结束，则返回 null
    If Not tokenScanner.hasNext Then
        Set parseCastFunc = Nothing
        Exit Function
    End If

    ' 2. 如果开头不是 cast，则立刻结束
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_CAST Then
        Set parseCastFunc = Nothing
        Exit Function
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Set parseCastFunc = Nothing
        Exit Function
    End If

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 3. 如果下一个不是左括号，则立刻中止
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.LEFT_PARENTHESIS Then
        Set parseCastFunc = Nothing
        Exit Function
    End If
    tokenScanner.toNext
    parenthesisNestCount = parenthesisNestCount + 1

    ' 4. 尝试匹配一个列
    Dim result As ISqlTranslateColInfo
    Set result = parseSelectColInfo(tokenScanner)

    ' 5. 无论 4 的匹配结果如何（有或没有都忽略），继续向后搜索直到搜索到下一个配对的 [)] 为止
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        If parenthesisNestCount <= nestCountBackup Then
            Goto while1Break
        End If

        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 如果遇到了 ; 则立刻停止
        If tokenType = SqlTokenType.SEMICOLON Then
            Goto while1Break
        End If

        ' 如果遇到了 [)], 则返回减小括号嵌套深度
        If tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            parenthesisNestCount = parenthesisNestCount - 1
        End If

        ' 如果遇到其他类型的 token，全部忽略
        tokenScanner.toNext
    Wend
while1Break:

    If Not result Is Nothing Then
        Set parseCastFunc = result
    Else
        ' 如果匹配结果为空，则可能遇到了一个没有参数的 cast 函数声明，如: `Cast()`
        ' 这种情况下，需要创建一个空列
        ' TODO 是否应该使用空 SqlTranslateSingleColInfo 对象 ?????
        Dim singleColInfo As SqlTranslateSingleColInfo
        Set singleColInfo = New SqlTranslateSingleColInfo
        Set parseCastFunc = singleColInfo
    End If
End Function

' 解析 Between 中的参数
' 需要匹配的内容
'     三元比较 : between elem1 and elem2
'                elem1 + elem2 是 mixedColExpr
'                   这里的 mixedColExpr，在无括号的情况下，不能出现逻辑运算符
'                   在有括号的情况下，后面如果是
' @return IObjArrayRef<ISqlTranslateColInfo>
Private Function parseBetweenParam(ByRef tokenScanner As ObjArrayRefScanner) As IObjArrayRef
    ' 1. 如果 token 扫描结束，则返回 null
    If Not tokenScanner.hasNext Then
        Set parseBetweenParam = Nothing
        Exit Function
    End If

    ' 2. 如果当前 token 不是 between ，则立刻停止
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_BETWEEN Then
        Set parseBetweenParam = Nothing
        Exit Function
    End If
    tokenScanner.toNext

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 3. 解析 elem1
    Dim result As IObjArrayRef
    Set result = Nothing

    Dim tempColInfo As ISqlTranslateColInfo
    Set tempColInfo = parseMixedColExpr(tokenScanner, True)
    If Not tempColInfo Is Nothing Then
        Set result = UtilsArrayRef.createRealObjArrayRef(2)
        result.Add tempColInfo
    End If

    ' 4. 解析 and
    If Not tokenScanner.hasNext Then
        Set parseBetweenParam = result
        Exit Function
    End If

    ' 如果这里没有找到一个 and ，则说明当前出现了语法错误
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_AND Then
        Set parseBetweenParam = result
        Exit Function
    End If
    tokenScanner.toNext

    ' 5. 解析 elem2
    Set tempColInfo = parseMixedColExpr(tokenScanner, True)
    If Not tempColInfo Is Nothing Then
        If result Is Nothing Then
            Set result = UtilsArrayRef.createRealObjArrayRef(1)
        End If

        result.Add tempColInfo
    End If

    Set parseBetweenParam = result
End Function

' 解析 in 运算符的参数
' 需要匹配的内容
'     in (elem1,.., elemN)
'           elem1,.., elemN 可以是 mixedColExpr
'           包含逻辑表达式的情况: true in ('a'='a' and 'b'='b', false)
'           in 后面必须跟随括号，否则按照错误语法处理
' 需要注意的内容
'      对于 in 后面的括号，只能处理一层，剩余的括号都属于 mixedColExpr
' @return IObjArrayRef<ISqlTranslateColInfo>
Private Function parseCompareInParam(ByRef tokenScanner As ObjArrayRefScanner) As IObjArrayRef
    ' 1. 如果 token 扫描结束，则返回 null
    If Not tokenScanner.hasNext Then
        Set parseCompareInParam = Nothing
        Exit Function
    End If

    ' 2. 如果当前 token 不是 in ，则立刻停止
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_IN Then
        Set parseCompareInParam = Nothing
        Exit Function
    End If
    tokenScanner.toNext

    ' 3. in 后面必须是一个 [(]
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.LEFT_PARENTHESIS Then
        Set parseCompareInParam = Nothing
        Exit Function
    End If
    tokenScanner.toNext

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount
    parenthesisNestCount = parenthesisNestCount + 1

    ' 4. 忽略开头的 [,], 以兼容语法错误
    '    如 xxx in (, , , 'xxx')
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        If token.tokenType <> SqlTokenType.COMMA Then
            Goto filterCommaBreak
        End If
        tokenScanner.toNext
    Wend
filterCommaBreak:

    ' 5.处理各个比较参数
    Dim result As IObjArrayRef
    Set result = Nothing

    Dim tempColInfo As ISqlTranslateColInfo
    Set tempColInfo = Nothing
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        ' 如果遇到了 [;], 则立刻停止
        If token.tokenType = SqlTokenType.SEMICOLON Then
            Goto while1Break
        End If

        ' 尝试匹配一个 mixedColExpr
        Set tempColInfo = parseMixedColExpr(tokenScanner)
        If Not tempColInfo Is Nothing Then
            If result Is Nothing Then
                Set result = UtilsArrayRef.createRealObjArrayRef(5)
            End If

            result.Add tempColInfo
        End If

        ' 再尝试匹配一个 [,]。如果已经不是[,] 了，则退出遍历
        If Not tokenScanner.hasNext Then
            Goto while1Break
        End If

        Set token = tokenScanner.peek
        If token.tokenType <> SqlTokenType.COMMA Then
            Goto while1Break
        End If

        tokenScanner.toNext
    Wend
while1Break:

    ' 6. 定位到下一个配对的 [)] （如果可以的话）
    If nestCountBackup > parenthesisNestCount Then
        While tokenScanner.hasNext
            If parenthesisNestCount <= nestCountBackup Then
                Goto while2Break
            End If

            Set token = tokenScanner.peek

            If token.tokenType = SqlTokenType.SEMICOLON Then
                Goto while2Break
            ElseIf token.tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
                parenthesisNestCount = parenthesisNestCount - 1
            End If

            tokenScanner.toNext
        Wend
while2Break:
    End If

    Set parseCompareInParam = result
End Function

' 处理 case 表达式
' 需要匹配的内容
' caseStatement -> case [mixedColExpr] when mixedColExpr then mixedColExpr  <- 关键字 case 开头
'                  [when mixedColExpr then mixedColExpr]
'                  [else mixedColExpr]
'                  end
Private Function parseCaseStatement(ByRef tokenScanner As ObjArrayRefScanner) As SqlTranslateCompositeColInfo
    ' 1. 如果 token 扫描结束，则返回 null
    If Not tokenScanner.hasNext Then
        Set parseCaseStatement = Nothing
        Exit Function
    End If

    ' 2. 如果当前 token 不是 case，则立刻停止
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_CASE Then
        Set parseCaseStatement = Nothing
        Exit Function
    End If
    tokenScanner.toNext

    ' 3.0 遍历开始
    ' 3.1 如果是 when，token 向后推进，然后回到 3.0 继续
    ' 3.2 如果是 then，token 向后推进，然后回到 3.0 继续
    ' 3.3 如果是 else，token 向后推进，然后回到 3.0 继续
    ' 3.4 如果是 end，token 向后推进，然后退出
    ' 3.5 如果遇到了[;], 说明当前正在处理的 sql 片段已经结束了，立刻停止匹配
    '     (快速结束)
    ' 3.6 如果遇到了[)], 说明可能遇到了当前作用域片段的结束，立刻停止匹配
    '         正常的[)] 应该在解析 mixedColExpr 时，就已经解析完成了，多出来的一定是作用域的结束标志
    '         在没有遇到 end 时遇到 [])],说明这是语法错误，为了能够继续执行，忽略错误
    ' 3.7 如果是其他内容，则去匹配列表达式
    '
    ' 每次遍历结束前的检查
    '    如果: 当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
    '         以防止出现语法错误导致的无法停止解析 case
    '         这里不能包括 =，以防止未解析玩就立刻退出的行为

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    Dim result As SqlTranslateCompositeColInfo
    Set result = Nothing

    Dim tempCol As ISqlTranslateColInfo
    Set tempCol = Nothing

    Dim tokenType As SqlTokenType
    while tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        Select Case tokenType
            Case SqlTokenType.KEY_WHEN
                ' 3.1 如果是 when，token 向后推进，然后回到 3.0 继续
                tokenScanner.toNext
            Case SqlTokenType.KEY_THEN
                ' 3.2 如果是 then，token 向后推进，然后回到 3.0 继续
                tokenScanner.toNext
            Case SqlTokenType.KEY_ELSE
                ' 3.3 如果是 else，token 向后推进，然后回到 3.0 继续
                tokenScanner.toNext
            Case SqlTokenType.KEY_END
                ' 3.4 如果是 end，token 向后推进，然后退出
                tokenScanner.toNext
                Goto matchToken
            Case SqlTokenType.SEMICOLON
                ' 3.5 如果遇到了[;], 说明当前正在处理的 sql 片段已经结束了，立刻停止匹配
                Goto matchToken
            Case SqlTokenType.RIGHT_PARENTHESIS
                ' 3.6 如果遇到了[)], 说明可能遇到了当前作用域片段的结束，立刻停止匹配
                Goto matchToken
            Case Else
                ' 3.7 如果是其他内容，则去匹配列表达式
                Set tempCol = parseMixedColExpr(tokenScanner)
                If Not tempCol Is Nothing Then
                    If result Is Nothing Then
                        Set result = New SqlTranslateCompositeColInfo
                    End If

                    result.addColInfo tempCol
                End If
        End Select

        ' 每次遍历结束前的检查
        '    如果: 当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
        '    这里不能包括 =，以防止未解析玩就立刻退出的行为
        If parenthesisNestCount < nestCountBackup Then
            Goto matchToken
        End If
    Wend
matchToken:

    Set parseCaseStatement = result
End Function

' 匹配函数声明
' 需要匹配的内容:
'      function -> func(funcParams)  <- STR_FUNC 开头
' @return IObjArrayRef<ISqlTranslateColInfo>
Private Function parseFunction(ByRef tokenScanner As ObjArrayRefScanner) As SqlTranslateCompositeColInfo
    ' 1. 如果 token 扫描结束，则返回 null
    If Not tokenScanner.hasNext Then
        Set parseFunction = Nothing
        Exit Function
    End If

    ' 2. 如果当前 token 不是 func，则立刻停止
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.STR_FUNC Then
        Set parseFunction = Nothing
        Exit Function
    End If
    tokenScanner.toNext

    ' 3. 开始匹配
    '    开始时，默认创建一个空的复合列对象，以应对没有写函数参数的情况
    Set parseFunction = new SqlTranslateCompositeColInfo

    ' 4. 保存当前的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 5.0 遍历开始
    ' 5.1 如果是左括号 [(]
    '      增加括号嵌套数量，然后回到 5.0 继续
    ' 5.2 如果是右括号 [)]
    ' 5.2.1 当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
    '          发生这种情况时，有可能出现了语法错误
    '          此时 token 扫描器不再向前推进，因为已经打破了括号作用域的嵌套数量，这里无法处理
    ' 5.2.2 否则减少嵌套数量
    '          减少后再次检查，如果 当前的括号嵌套数量 == nestCountBackup，则终止遍历
    '              这种情况下，说明 func(...) 的形式已经结束
    ' 5.3 如果是其他内容，则去匹配函数参数
    '         匹配完成后，立刻结束

    ' 5.0 遍历开始
    Dim tokenType As SqlTokenType
    Dim scannerIndexBackup As Long
    while tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        Select Case tokenType
            Case SqlTokenType.LEFT_PARENTHESIS
                ' 5.1 如果是左括号 [(]
                '     增加括号嵌套数量，然后回到 5.0 继续
                parenthesisNestCount = parenthesisNestCount + 1
                tokenScanner.toNext
            Case SqlTokenType.RIGHT_PARENTHESIS
                ' 5.2 如果是右括号 [)]

                ' 5.2.1 当前的括号嵌套数量 <= nestCountBackup ，则终止遍历
                '       发生这种情况时，有可能出现了语法错误
                '       此时 token 扫描器不再向前推进，因为已经打破了括号作用域的嵌套数量，这里无法处理
                If parenthesisNestCount <= nestCountBackup Then
                    Goto matchToken
                End If

                ' 5.2.2 否则减少嵌套数量
                '          减少后再次检查，如果 当前的括号嵌套数量 == nestCountBackup，则终止遍历
                '              这种情况下，说明 func(...) 的形式已经结束
                parenthesisNestCount = parenthesisNestCount - 1
                tokenScanner.toNext

                If parenthesisNestCount = nestCountBackup Then
                    Goto matchToken
                End If
            Case Else
                ' 5.3 如果是其他内容，则去匹配函数参数
                scannerIndexBackup = tokenScanner.ActiveIndex
                parseFuncParams parseFunction, tokenScanner

                ' 如果活动索引没有发生变化，则手动向前推进（以兼容语法错误）
                If scannerIndexBackup = tokenScanner.ActiveIndex Then
                    tokenScanner.toNext
                End If
        End Select
    Wend
matchToken:

End Function

' 匹配函数参数
' 需要匹配的内容
'     funcParams -> mixedColExpr,mixedColExpr | mixedColExpr
' 转换后的匹配内容
'     funcParams -> mixedColExpr R
'                 | E
'              R -> , mixedColExpr | E
' @return IObjArrayRef<ISqlTranslateColInfo>
Private Sub parseFuncParams( _
    ByRef compositeColInfo As SqlTranslateCompositeColInfo, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果 token 扫描结束，则返回 null
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 保存当前的括号数量，以防止匹配 mixedColExpr 时出现语法异常
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 3. 首先匹配 funcParams -> mixedColExpr R
    Dim paramInfo As ISqlTranslateColInfo
    Set paramInfo = parseMixedColExpr(tokenScanner)
    compositeColInfo.addColInfo paramInfo

    ' 如果 paramInfo 是 null，这里也不当作匹配失败，暂时当作语法异常
    ' 如，可能会出现: fun(, xxx, xx) 的情况

    ' 4. 检查括号数量
    If parenthesisNestCount < nestCountBackup Then
        Exit Sub
    End If

    ' 5. 匹配: R -> , mixedColExpr | E
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 5.0 快速结束
        '     如果遇到了 [)] 则立刻结束
        If tokenType <> SqlTokenType.RIGHT_PARENTHESIS Then
            Goto while1Break
        End If

        ' 5.1 首先匹配[,]，如果无法匹配则立刻停止
        If tokenType <> SqlTokenType.COMMA Then
            Goto while1Break
        End If
        tokenScanner.toNext

        ' 5.2 尝试匹配 mixedColExpr，这里转换成匹配:
        '     mixedColExpr -> colExpr R
        '     以降低递归调用的消耗
        Set paramInfo = parseMixedColExpr(tokenScanner)
        compositeColInfo.addColInfo paramInfo

        ' 5.3 检查括号数量，以防止语法错误
        If parenthesisNestCount < nestCountBackup Then
            Goto while1Break
        End If
    Wend
while1Break:

End Sub

' 匹配列名部分
' 需要匹配的内容
'   colName -> name
'            | tableName.name
Private Function parseColName(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateColInfo
    ' 如果片段已经结束，则立刻停止匹配
    If Not tokenScanner.hasNext Then
        Set parseColName = Nothing
        Exit Function
    End If

    ' 1. 检查第一个 token
    ' 1.1 如果是 str_name，则需要继续搜索，以确定这个 token 是 tableName 还是 colNameVal
    '        扫描器需要向前推进
    ' 1.2 如果是 [*]，则创建一个全数据列，并返回
    '        扫描器需要向前推进
    ' 1.3 如果是其他情况，则立刻停止
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.STR_NAME Then
        tokenScanner.toNext
    ElseIf token.tokenType = SqlTokenType.ASTERISK Then
        tokenScanner.toNext
        Set parseColName = createAsteriskColInfo(Nothing, Nothing, token)
        Exit Function
    Else
        ' return null
        Exit Function
    End If

    ' 从 1.1 进入这里
    ' 如果片段已经结束了，则说明当前 token 就是列名
    If Not tokenScanner.hasNext Then
        Set parseColName = createSingleColInfo(Nothing, Nothing, token)
        Exit Function
    End If

    ' 2. 如果第二个不是 [.], 则说明第一个 token 就是列名，
    '    匹配到了: colName -> name, 创建 colInfo 后立刻停止
    Dim pointToken As SqlToken
    Set pointToken = tokenScanner.peek
    If pointToken.tokenType <> SqlTokenType.POINT Then
        Set parseColName = createSingleColInfo(Nothing, Nothing, token)
        Exit Function
    End If
    tokenScanner.toNext

    ' 如果片段已经结束了，说明当前的这部分内容不符合语法，但是为了能够继续执行
    ' 默认将第一个 token 当作表名来创建 colInfo
    If Not tokenScanner.hasNext Then
        Set parseColName = createSingleColInfo(token, pointToken, Nothing)
        Exit Function
    End If

    ' 3. 检查第三个 token
    ' 3.1 如果是 str_name, 则匹配到了: [tableName.str_name]，则创建单列对象
    '         扫描器向前推进
    ' 3.2 如果是 [*], 则匹配到了: [tableName.*], 则创建全数据列对象
    '         扫描器向前推进
    ' 3.3 如果是其他内容，则说明存在语法错误，只匹配到了: [tableName.]，创建一个单列对象
    Dim token2 As SqlToken
    Set token2 = tokenScanner.peek
    If token2.tokenType = SqlTokenType.STR_NAME Then
        tokenScanner.toNext
        Set parseColName = createSingleColInfo(token, pointToken, token2)
    ElseIf token2.tokenType = SqlTokenType.ASTERISK Then
        tokenScanner.toNext
        Set parseColName = createAsteriskColInfo(token, pointToken, token2)
    Else
        Set parseColName = createSingleColInfo(token, pointToken, Nothing)
    End If

End Function

' ======================================================
' 解析 update
' ======================================================
' 解析 update sql 片段，并生成作用域
' 需要匹配的内容
'    参考: https://dev.mysql.com/doc/refman/8.1/en/update.html
'    updateStatement --> UPDATE [LOW_PRIORITY] [IGNORE] table_reference
'                        SET assignment_list
'                        [WHERE where_condition]
'                        [ORDER BY ...]
'                        [LIMIT row_count]
Private Function parseUpdate(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateEnv
    ' 1. 检查开始标记
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 2. 在开始处，需要循环处理可能会出现的 [(] + [with]
    parseMultiWith tokenScanner

    ' 3. 解析一个 update 语句，并生成一个活动作用域
    Set parseUpdate = doParseUpdate(tokenScanner)

    ' 4. 检查: 如果当括号嵌套深度别下是存在可用的 withEnvs，则将这一深度的所有 withEnvs 都保存到 [活动作用域]
    Set parseUpdate = saveWithEnvsToEnv(parseUpdate, parenthesisNestCount, True)

    ' 5. 检查: 如果当前括号嵌套深度 > [nestCountBackup]，则尝试退出 [)]，并保存每一层的 with 到活动作用域
    '     - 退出的最小值为 [nestCountBackup]
    '     - 如果发生语法异常导致无法退出到 [nestCountBackup]，则忽略
    If parenthesisNestCount > nestCountBackup Then
        Set parseUpdate = exitRightParenthesisAndWithEnv(tokenScanner, parseUpdate, nestCountBackup)
    End If

End Function

' 解析一个 update 语句
Private Function doParseUpdate(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateEnv
    ' 1. 检查第一个 token 是不是 update，如果不是，则立刻结束
    Dim token As SqlToken
    Set token = tokenScanner.peek
    ' 1.1 如果不是，则立刻结束
    If token.tokenType <> SqlTokenType.KEY_UPDATE Then
        ' return null
        Exit Function
    End If
    ' 1.2 跳过 update 这个 token
    tokenScanner.toNext

    ' 2. 保存 update 的开始时的括号嵌套深度 [updateStartNestCount = parenthesisNestCount]
    Dim updateStartNestCount As Integer
    updateStartNestCount = parenthesisNestCount

    ' 3. 过滤掉 update 后面可能出现的关键字:
    '    LOW_PRIORITY, IGNORE
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        If token.tokenType <> SqlTokenType.KEY_LOW_PRIORITY _
            And token.tokenType <> SqlTokenType.KEY_IGNORE Then

            Goto outStartKeyBreak
        End If
        tokenScanner.toNext
    Wend
outStartKeyBreak:

    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 4. 初始化一个作用域对象
    Dim result As SqlTranslateSnippetsEnv
    Set result = New SqlTranslateSnippetsEnv
    result.Init SqlTokenType.KEY_UPDATE

    ' 在这里提前设置 return result 的值，以减少后面每次 return 时的单独设置
    Set doParseUpdate = result

    ' 5. 匹配后面将会出现的连续表定义
    parseTableReferences result, tokenScanner
    If Not tokenScanner.hasNext Then
        Exit Function
    End If

    ' 5. 匹配结束后，检查 [()] 的嵌套深度
    ' 5.1 `updateStartNestCount > parenthesisNestCount`, 则退出，并返回 [活动作用域]
    If updateStartNestCount > parenthesisNestCount Then
        Exit Function
    End If

    ' 5.2 如果没有 token 了, 则退出，并返回 [活动作用域]
    If Not tokenScanner.hasNext Then
        Exit Function
    End If

    ' 6. 过滤掉下一个 set token
    Set token = tokenScanner.peek
    ' 6.1 如果下一个 token 不是 set, 则退出，并返回 [活动作用域]
    If token.tokenType <> SqlTokenType.KEY_SET Then
        Exit Function
    End If

    ' 6.2.1 跳过这个 token
    tokenScanner.toNext

    ' 6.2.2 检查: 如果已经没有 token 了，则跳转到 10
    If Not tokenScanner.hasNext Then
        Exit Function
    End If

    ' 7. 匹配 assignment_list
    parseAssignmentList result, tokenScanner

    ' 7.1 `updateStartNestCount > parenthesisNestCount`, 部分越界，则跳转到 10
    If updateStartNestCount > parenthesisNestCount Then
        Exit Function
    End If

    ' 7.2 如果没有 token 了，则跳转到 10
    If Not tokenScanner.hasNext Then
        Exit Function
    End If

    ' 8. 匹配剩余的内容
    Dim scannerIndexBackup As Long
    Dim tokenType As SqlTokenType
    ' 8.1 遍历 token
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        If tokenType = SqlTokenType.SEMICOLON Then
            ' 8.2 如果发现了[;], 则退出遍历
            Goto matchTokenBreak
        End If

        ' 8.3 处理 update 的组成部分
        ' 8.3.1 记录扫描器的活动索引 [scannerIndexBackup]
        scannerIndexBackup = tokenScanner.ActiveIndex

        If tokenType = SqlTokenType.KEY_WHERE Then
            ' 8.3.2 如果发现了 where ，则匹配 where 表达式
            parseWhereColInfos result, tokenScanner
        ElseIf tokenType = SqlTokenType.KEY_ORDER_BY Then
            ' 8.3.3 如果发现了 order ，则匹配 order by 表达式
            parseOrderByColInfos result, tokenScanner
        ElseIf tokenType = SqlTokenType.KEY_LIMIT Then
            ' 8.3.4 如果发现了 limit ，则匹配 limit 表达式
            skipLimit tokenScanner
        Else
            ' 8.3.5 如果遇到了其他内容，则退出遍历
            Goto matchTokenBreak
        End If

        ' 9.7 匹配结束后，执行检查
        ' 9.7.1 检查匹配或的扫描器活动索引
        '         如果没有发生移动，则说明这一圈中没有匹配到任何内容，立刻结束
        If scannerIndexBackup = tokenScanner.ActiveIndex Then
            Goto matchTokenBreak
        End If

        ' 8.4 匹配结束后，执行检查
        ' 8.4.1 如果扫描器活动索引，没有发生移动，则说明这一圈中没有匹配到任何内容，退出遍历
        If scannerIndexBackup = tokenScanner.ActiveIndex Then
            Goto matchTokenBreak
        End If

        ' 8.4.2 如果 `updateStartNestCount > parenthesisNestCount`, 则退出遍历
        If updateStartNestCount > parenthesisNestCount Then
            Goto matchTokenBreak
        End If

    Wend
matchTokenBreak:
End Function

' 匹配 update 中的设值表达式列表
' 需要匹配的内容:
'   assignment_list -> assignment_list, assignment
' 转换形式
'   assignment_list -> assignment R
'                 R -> , assignment_list R | E
' 停止标志:
'     WHERE, ORDER, BY, LIMIT
Private Sub parseAssignmentList(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 检查开始标记
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 尝试匹配第一个设值表达式
    parseAssignment env, tokenScanner
    ' 这里忽略对错误的检查，并且无论是否发生错误都不停止
    ' 这些处理全部在 3 中执行

    ' 3. 匹配 R
    ' 3.1 遍历 token
    ' 3.2 如果遇到 [,]
    ' 3.2.1 跳过 [,]
    ' 3.2.2 继续向下匹配下一个设值表达式
    ' 3.2.3 匹配结束后，如果这一次没有匹配到任何列对象，则立刻结束
    ' 3.2.4 然后回到 3.1 继续，尝试匹配下一个设值表达式
    ' 3.3 如果遇到其他 token，则立刻停止

    ' 3.1 遍历 token
    Dim token As SqlToken
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        If tokenType = SqlTokenType.COMMA Then
            ' 3.2 如果遇到 [,]
            ' 3.2.1 跳过 [,]
            '       在这里尝试跳过后面连续出现的 [,]
            Do
                tokenScanner.toNext

                If Not tokenScanner.hasNext Then
                    GoTo matchTokenBreak
                End If

                Set token = tokenScanner.peek
            Loop While token.tokenType = SqlTokenType.COMMA

            ' 3.2.2 继续向下匹配下一个设值表达式
            If Not parseAssignment(env, tokenScanner) Then
                ' 3.2.3 匹配结束后，如果这一次没有匹配到任何列对象，则立刻结束
                Goto matchTokenBreak
            End If

            ' 3.2.4 然后回到 3.1 继续，尝试匹配下一个设值表达式

        Else
            ' 3.3 如果遇到其他 token，则立刻停止
            Goto matchTokenBreak
        End If
    Wend
matchTokenBreak:

End Sub

' 匹配 update 中的设值表达式
' 需要匹配的内容:
'   assignment -> colExpr = mixedColExpr
'
' 所有筛选出来的列全部保存到作用域的添加列中
' @return 表示在当前方法内是否匹配到了某个列对象
'         true = 匹配到了一个或多个列对象
'         false = 匹配失败
Private Function parseAssignment(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner) As Boolean
    parseAssignment = False

    ' 1. 检查开始标记
    If Not tokenScanner.hasNext Then
        ' return false
        Exit Function
    End If

    ' 2. 匹配一个 colExpr
    '    如果返回了一个 null，则说明匹配失败，立刻结束
    Dim col As ISqlTranslateColInfo
    Set col = parseColExpr(tokenScanner)
    If col Is Nothing Then
        'return false
        Exit Function
    End If
    env.addConditionColInfo col

    ' 3. 匹配 [=]
    '    如果匹配失败，说明可能存在语法错误，立刻结束
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.OPERA_EQ Then
        parseAssignment = True
        Exit Function
    End If
    tokenScanner.toNext

    ' 4. 匹配一个 mixedColExpr
    Set col = parseMixedColExpr(tokenScanner)
    If Not col Is Nothing Then
        env.addConditionColInfo col
    End If

    parseAssignment = True
End Function

' ======================================================
' 快捷方式
' ======================================================
' 快捷方式，创建一个单列对象
Private Function createSingleColInfo( _
    ByRef tableNameToken As SqlToken, _
    ByRef pointToken As SqlToken, _
    ByRef colNameToken As SqlToken) As SqlTranslateSingleColInfo
    Dim result As SqlTranslateSingleColInfo
    Set result = New SqlTranslateSingleColInfo

    ' 将非空 token 对象保存到结果中
    If Not tableNameToken Is Nothing Then
        result.setTableNameToken tableNameToken
    End If

    ' [.]
    If Not pointToken Is Nothing Then
        result.setPointToken pointToken
    End If

    ' colName
    If Not colNameToken Is Nothing Then
        result.setColNameToken colNameToken
    End If

    Set createSingleColInfo = result
End Function

' 合并两个列对象
Private Function mergeColInfoWithArray( _
    ByRef origin As ISqlTranslateColInfo, ByRef listColInfo As IObjArrayRef) As ISqlTranslateColInfo

    ' 1. 如果数组引用中没有内容，则返回 origin
    If listColInfo Is Nothing Then
        Set mergeColInfoWithArray = origin
        Exit Function
    End If

    If listColInfo.Count = 0 Then
        Set mergeColInfoWithArray = origin
        Exit Function
    End If

    ' 2. 进行合并
    ' 2.1 如果 origin 是空，则只合并数组中的内容
    Dim result As SqlTranslateCompositeColInfo
    Dim i As Long
    Dim c As ISqlTranslateColInfo
    If origin Is Nothing Then
        If listColInfo.Count = 1 Then
            ' 2.1.1 如果只有 1 个，则直接返回这个列对象
            Set mergeColInfoWithArray = listColInfo.Item(0)
            Exit Function
        Else
            ' 2.1.2 如果有多个，需要将这个多个列对象合并成一个复合列结构
            Set result = New SqlTranslateCompositeColInfo
            With listColInfo
                For i = 0 To .MaxIndex
                    Set c = .Item(i)
                    result.addColInfo c
                Next
            End With

            Set mergeColInfoWithArray = result
            Exit Function
        End If
    Else
        If Typeof origin Is SqlTranslateCompositeColInfo Then
            ' 2.2 如果 origin 是复合列结构，则将数组中的列对象全部合并到 origin 中
            Set result = origin
            With listColInfo
                For i = 0 To .MaxIndex
                    Set c = .Item(i)
                    result.addColInfo c
                Next
            End With

            Set mergeColInfoWithArray = result
            Exit Function
        Else
            ' 2.3 如果 origin 是一个单列结构，则需要创建一个复合列结构，来保存 origin + arr
            Set result = New SqlTranslateCompositeColInfo
            result.addColInfo origin

            With listColInfo
                For i = 0 To .MaxIndex
                    Set c = .Item(i)
                    result.addColInfo c
                Next
            End With

            Set mergeColInfoWithArray = result
            Exit Function
        End If
    End If
End Function

' 合并两个列对象
Private Function mergeColInfo( _
    ByRef origin As ISqlTranslateColInfo, ByRef other As ISqlTranslateColInfo) As ISqlTranslateColInfo

    ' 1. 如果合并对象是 null，则立刻结束
    If other Is Nothing Then
        Set mergeColInfo = origin
        Exit Function
    End If

    ' 2. 进行合并
    ' 2.1 如果 origin 是空，则直接返回 other
    If origin Is Nothing Then
        Set mergeColInfo = other
        Exit Function
    Else
        Dim result As SqlTranslateCompositeColInfo
        If Typeof origin Is SqlTranslateCompositeColInfo Then
            Set result = origin
            ' 2.2 如果 origin 是复合列结构，则直接合并 other
            result.addColInfo other

            Set mergeColInfo = result
            Exit Function
        Else
            ' 2.3 如果 origin 是一个单列结构，则需要创建一个复合列结构，来保存 origin + arr
            Set result = New SqlTranslateCompositeColInfo
            result.addColInfo origin
            result.addColInfo other

            Set mergeColInfo = result
            Exit Function
        End If
    End If
End Function

' 将一个 select 作用域保证成一个子查询列对象
Private Function wrapperSelectEnvToColInfo(ByRef env As ISqlTranslateEnv) As SqlTranslateSubSelectColInfo
    ' 如果参数是空，则不进行包装，仍然返回 null
    If env Is Nothing Then
        Set wrapperSelectEnvToColInfo = Nothing
        Exit Function
    End If

    ' 将当前子查询保证成子查询列
    Set wrapperSelectEnvToColInfo = New SqlTranslateSubSelectColInfo
    wrapperSelectEnvToColInfo.Init env
End Function

' 创建: 字面量列结构
Private Function createLiteralColInfo(ByRef token As SqlToken) As SqlTranslateLiteralColInfo
    Set createLiteralColInfo = New SqlTranslateLiteralColInfo
    createLiteralColInfo.Init token
End Function

' 创建: 全数据列的列结构
' @param tableNameToken 表名的 token
' @param pointToken [.] 的 token
' @param asteriskToken [*] 的 token
Public Function createAsteriskColInfo( _
    ByRef tableNameToken As SqlToken, ByRef pointToken As SqlToken, _
    ByRef asteriskToken As SqlToken) As SqlTranslateAsteriskColInfo

    Set createAsteriskColInfo = new SqlTranslateAsteriskColInfo

    ' 保存不为空的 token
    If Not tableNameToken Is Nothing Then
        createAsteriskColInfo.setTableNameToken tableNameToken
    End If

    if Not pointToken Is Nothing Then
        createAsteriskColInfo.setPointToken pointToken
    End If

    If Not asteriskToken Is Nothing Then
        createAsteriskColInfo.setAsteriskToken asteriskToken
    End If
End Function

' 直接将 token 包装成真实表的表级作用域
Private Function createRealTableEnv(ByRef nameToken As SqlToken, Optional ByRef aliasToken As SqlToken = Nothing) As SqlTranslateTableEnv
    Dim realTableInfo As SqlTranslateRealTableInfo
    Set realTableInfo = New SqlTranslateRealTableInfo
    realTableInfo.Init nameToken

    If Not aliasToken Is Nothing Then
        realTableInfo.ISqlTranslateTableInfo_setAliasNameToken aliasToken
    End If

    Set createRealTableEnv = New SqlTranslateTableEnv
    createRealTableEnv.Init realTableInfo
End Function

' 将片段级作用域保证成表级作用域
Private Function wrapperSnippetsEnvToTableEnv(ByRef e As SqlTranslateSnippetsEnv) As SqlTranslateTableEnv
    Dim selectTableInfo As SqlTranslateSelectTableInfo
    Set selectTableInfo = New SqlTranslateSelectTableInfo
    selectTableInfo.Init e

    Set wrapperSnippetsEnvToTableEnv = New SqlTranslateTableEnv
    wrapperSnippetsEnvToTableEnv.Init selectTableInfo
End Function

' ======================================================
' 解析 delete
' ======================================================
' 解析 delete sql 片段，并生成作用域
Private Function parseDelete(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateEnv
    ' 1. 检查开始标记
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 记录: 开始时的括号数量
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 2. 在开始处，需要循环处理可能会出现的 [(] + [with]
    parseMultiWith tokenScanner

    ' 3. 解析一个 delete 语句，并生成一个活动作用域
    Set parseDelete = doParseDelete(tokenScanner)

    ' 4. 检查: 如果当括号嵌套深度别下是存在可用的 withEnvs，则将这一深度的所有 withEnvs 都保存到 [活动作用域]
    Set parseDelete = saveWithEnvsToEnv(parseDelete, parenthesisNestCount, True)

    ' 5. 检查: 如果当前括号嵌套深度 > [nestCountBackup]，则尝试退出 [)]，并保存每一层的 with 到活动作用域
    '     - 退出的最小值为 [nestCountBackup]
    '     - 如果发生语法异常导致无法退出到 [nestCountBackup]，则忽略
    If parenthesisNestCount > nestCountBackup Then
        Set parseDelete = exitRightParenthesisAndWithEnv(tokenScanner, parseDelete, nestCountBackup)
    End If

End Function

' 解析 delete sql 片段，并生成作用域
' 需要匹配的内容
'    参考: https://dev.mysql.com/doc/refman/5.7/en/delete.html
'
' 情况1 - 单表删除
' DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name
'     [PARTITION (partition_name [, partition_name] ...)]
'     [WHERE where_condition]
'     [ORDER BY ...]
'     [LIMIT row_count]
'
' 情况2 - 多表删除1
' DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
'     tbl_name[.*] [, tbl_name[.*]] ...
'     FROM table_references
'     [WHERE where_condition]
'
' 情况3 - 多表删除2
' DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
'     FROM tbl_name[.*] [, tbl_name[.*]] ...
'     USING table_references
'     [WHERE where_condition]
Private Function doParseDelete(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateEnv
    ' 检查参数
    If Not tokenScanner.hasNext Then
        'return null
        Exit Function
    End If

    ' 1. 检查第一个 token 是不是 delete
    Dim token As SqlToken
    Set token = tokenScanner.peek

    ' 1.1 如果不是，则返回 null
    If token.tokenType <> SqlTokenType.KEY_DELETE Then
        'return null
        Exit Function
    End If

    ' 1.2 如果是，则跳过 delete 这个 token
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 2. 保存 delete 的开始时的括号嵌套深度 [startNestCount = parenthesisNestCount]
    Dim startNestCount As Integer
    startNestCount = parenthesisNestCount

    ' 3. 这里跳过可能会连续出现的删除行为关键字
    '    LOW_PRIORITY, QUICK, IGNORE
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType
        If tokenType <> SqlTokenType.KEY_LOW_PRIORITY _
            And tokenType <> SqlTokenType.KEY_QUICK _
            And tokenType <> SqlTokenType.KEY_IGNORE Then
                GoTo outActiveKeyBreak
        End If

        tokenScanner.toNext
    Wend
outActiveKeyBreak:

    '    如果过滤后没有 token 了, 则退出，并返回 null
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 4. 查看下一个 token，按照不同的情况来解析
    Dim result As SqlTranslateSnippetsEnv
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.KEY_FROM Then
        ' 4.1 下一个关键字是 from，则可能是[情况1] + [情况3]

        ' 4.1.1 跳过 from
        tokenScanner.toNext

        '  如果没有 token 了, 则退出，并返回 null
        If Not tokenScanner.hasNext Then
            'return null
            Exit Function
        End If

        ' 4.1.2 初始化一个 [活动作用域] 对象
        Set result = New SqlTranslateSnippetsEnv
        result.Init SqlTokenType.KEY_DELETE
        ' 提前设置 return result
        Set doParseDelete = result

        ' 4.1.3. 匹配后面的[表名]
        parseDeleteFromTableName result, tokenScanner
        ' 执行检查: [()] 的嵌套深度
        If startNestCount > parenthesisNestCount Then
            ' return result
            Exit Function
        End If
        '  如果没有 token 了, 则退出，并返回 [活动作用域]
        If Not tokenScanner.hasNext Then
            ' return result
            Exit Function
        End If

        ' 4.1.4 检查下一个 token
        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.KEY_USING Then
            ' 4.1.4.1 如果是 using，则匹配到了 [情况3]
            ' 4.1.4.1.1 如果是 using，则匹配到了 [情况3]
            tokenScanner.toNext
            If Not tokenScanner.hasNext Then
                ' return result
                Exit Function
            End If

            ' 4.1.4.1.2 匹配 table_references
            parseTableReferences result, tokenScanner
            ' 执行检查: [()] 的嵌套深度
            If startNestCount > parenthesisNestCount Then
                'return result
                Exit Function
            End If
            '  如果没有 token 了, 则退出，并返回 [活动作用域]
            If Not tokenScanner.hasNext Then
                ' return result
                Exit Function
            End If

            ' 4.1.4.1.3 匹配 where
            parseWhereColInfos result, tokenScanner
        Else
            ' 4.1.4.2 匹配到了 [情况3]
            ' 4.1.4.2.1 尝试匹配 where
            parseWhereColInfos result, tokenScanner
            ' 执行检查: [()] 的嵌套深度
            If startNestCount > parenthesisNestCount Then
                ' return result
                Exit Function
            End If
            '  如果没有 token 了, 则退出，并返回 [活动作用域]
            If Not tokenScanner.hasNext Then
                ' return result
                Exit Function
            End If

            ' 4.1.4.2.2 尝试匹配 order
            parseOrderByColInfos result, tokenScanner
            ' 执行检查: [()] 的嵌套深度
            If startNestCount > parenthesisNestCount Then
                ' return result
                Exit Function
            End If
            '  如果没有 token 了, 则退出，并返回 [活动作用域]
            If Not tokenScanner.hasNext Then
                ' return result
                Exit Function
            End If

            ' 4.1.4.2.3 尝试匹配 limit
            skipLimit tokenScanner
        End If
    ElseIf token.tokenType = SqlTokenType.STR_NAME Then
        ' 4.2 如果下一个关键字是 字符串，则尝试匹配[情况 2]
        ' 4.2.1 初始化一个 [活动作用域] 对象
        Set result = New SqlTranslateSnippetsEnv
        result.Init SqlTokenType.KEY_DELETE
        ' 提前设置 return result
        Set doParseDelete = result

        ' 4.2.2 匹配表名
        parseDeleteFromTableName result, tokenScanner
        ' 执行检查: [()] 的嵌套深度
        If startNestCount > parenthesisNestCount Then
            ' return result
            Exit Function
        End If
        '  如果没有 token 了, 则退出，并返回 [活动作用域]
        If Not tokenScanner.hasNext Then
            ' return result
            Exit Function
        End If

        ' 4.2.3 匹配 from
        parseFrom result, tokenScanner
        ' 执行检查: [()] 的嵌套深度
        If startNestCount > parenthesisNestCount Then
            ' return result
            Exit Function
        End If
        '  如果没有 token 了, 则退出，并返回 [活动作用域]
        If Not tokenScanner.hasNext Then
            ' return result
            Exit Function
        End If

        ' 4.2.4 匹配 where
        parseWhereColInfos result, tokenScanner
    End If
    ' 4.3
    ' return null // 在后面的 return result 一起处理

End Function

' 收集 From 后的所有表名
' 需要匹配的内容
' 模式1
'    tbl_name [[AS] tbl_alias]
'     [PARTITION (partition_name [, partition_name] ...)]
' 模式2
'    tbl_name[.*] [, tbl_name[.*]] ...
Private Sub parseDeleteFromTableName(ByRef env As SqlTranslateSnippetsEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 如果 token 扫描结束，则返回 null
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 搜索下一个 token
    ' 2.1 如果下一个 token 不是 str_name, 则立刻终止
    Dim nameToken As SqlToken
    Set nameToken = tokenScanner.peek
    If nameToken.tokenType <> SqlTokenType.STR_NAME Then
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        env.ISqlTranslateEnv_setMainEnv createRealTableEnv(nameToken)
        Exit Sub
    End If

    ' 2.2 检查是哪一种模式
    ' 如果下一个是 as 或者 str_name，则是模式 1
    Dim token As SqlToken
    Set token = tokenScanner.peek

    If token.tokenType = SqlTokenType.KEY_AS Then
        tokenScanner.toNext

        If Not tokenScanner.hasNext Then
            env.ISqlTranslateEnv_setMainEnv createRealTableEnv(nameToken)
            Exit Sub
        End If

        ' 如果下一个不是 name，则发生语法异常，立刻停止
        Set token = tokenScanner.peek
        If token.tokenType <> SqlTokenType.STR_NAME Then
            env.ISqlTranslateEnv_setMainEnv createRealTableEnv(nameToken)
            Exit Sub
        End If
        tokenScanner.toNext

        env.ISqlTranslateEnv_setMainEnv createRealTableEnv(nameToken, token)

        ' 匹配 partition
        skipPartition tokenScanner

        Exit Sub
    ElseIf token.tokenType = SqlTokenType.STR_NAME Then
        tokenScanner.toNext

        env.ISqlTranslateEnv_setMainEnv createRealTableEnv(nameToken, token)

        ' 匹配 partition
        skipPartition tokenScanner

        Exit Sub
    End If

    ' 否则是模式 2

    ' 将当前 nameToken 添加为表名 token
    env.addTableNameToken nameToken

    ' 2.3 尝试跳过 [.*]
    '     到这里只会是情况 2，不需要再次获取 token
    If token.tokenType = SqlTokenType.POINT Then
        tokenScanner.toNext

        If Not tokenScanner.hasNext Then
            Exit Sub
        End If

        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.ASTERISK Then
            tokenScanner.toNext
        End If
    End If

    ' 3. 匹配剩余的 [, tbl_name[.*]]
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        ' 3.1 如果不是comma，则立刻停止
        If token.tokenType <> SqlTokenType.COMMA Then
            GoTo matchTokenBreak
        End If

        tokenScanner.toNext
        If Not tokenScanner.hasNext Then
            GoTo matchTokenBreak
        End If

        ' 3.2 如果下一个 token 不是 str_name，则立刻退出
        '     不需要归还 [,]，当前语境下是语法错误
        '     不归还 [,] 不会影响后续的匹配
        Set token = tokenScanner.peek
        If token.tokenType <> SqlTokenType.STR_NAME Then
            GoTo matchTokenBreak
        End If
        tokenScanner.toNext

        ' 3.2.1 将当前 token 添加为表名 token
        env.addTableNameToken token

        ' 3.2.2 尝试跳过 [.*]
        If Not tokenScanner.hasNext Then
            GoTo matchTokenBreak
        End If
        Set token = tokenScanner.peek
        If token.tokenType = SqlTokenType.POINT Then
            tokenScanner.toNext

            If Not tokenScanner.hasNext Then
                GoTo matchTokenBreak
            End If

            Set token = tokenScanner.peek
            If token.tokenType = SqlTokenType.ASTERISK Then
                tokenScanner.toNext
            End If
        End If
    Wend
matchTokenBreak:
End Sub

' ======================================================
' 解析 insert
' ======================================================
' 解析 insert
Private Function parseInsert(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateEnv
    ' 检查参数
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 1. 记录: 开始时的括号数量 [nestCountBackup = parenthesisNestCount]
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 2. 在开始处，需要循环处理可能会出现的 [(]
    consumeLeftParenthesis tokenScanner

    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 3. 解析一个 insert 语句，并生成一个 [活动作用域]
    Set parseInsert = doParseInsert(tokenScanner)

    ' 4. 检查: 如果当前括号嵌套深度 > [nestCountBackup]，则尝试退出 [)]，并保存每一层的 with 到活动作用域
    '    - 退出的最小值为 [nestCountBackup]
    '    - 如果发生语法异常导致无法退出到 [nestCountBackup]，则忽略
    If parenthesisNestCount > nestCountBackup Then
        exitRightParenthesis tokenScanner, nestCountBackup
    End If
End Function

' 解析 insert sql 片段，并生成作用域
' 需要匹配的内容
'    参考: https://dev.mysql.com/doc/refman/8.1/en/insert.html
'
' 只匹配以下两种常用的形式
' 第一种形式:
'   INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
'       [INTO] tbl_name
'       [PARTITION (partition_name [, partition_name] ...)]
'       [(col_name [, col_name] ...)]
'       { {VALUES | VALUE} (value_list) [, (value_list)] ... }
'       [ON DUPLICATE KEY UPDATE assignment_list]
'    这里舍弃了以下内容
'      [AS row_alias[(col_alias [, col_alias] ...)]]
'
' 第二种形式:
'   INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
'       [INTO] tbl_name
'       [PARTITION (partition_name [, partition_name] ...)]
'       [(col_name [, col_name] ...)]
'       { SELECT ...
'         | TABLE table_name
'         | VALUES row_constructor_list
'       }
'       [ON DUPLICATE KEY UPDATE assignment_list]
Private Function doParseInsert(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateEnv
    ' 检查参数
    If Not tokenScanner.hasNext Then
        'return null
        Exit Function
    End If

    ' 1. 检查第一个 token 是不是 insert
    Dim token As SqlToken
    Set token = tokenScanner.peek

    ' 1.1 如果不是，则返回 null
    If token.tokenType <> SqlTokenType.KEY_INSERT Then
        ' return null
        Exit Function
    End If

    ' 1.2 如果是，则跳过 insert 这个 token
    tokenScanner.toNext

    ' 1.3 检查: 如果没有 token 了，则返回 null
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 2. 保存 insert 的开始时的括号嵌套深度 [startNestCount = parenthesisNestCount]
    Dim startNestCount As Integer
    startNestCount = parenthesisNestCount

    ' 3. 跳过开头的插入行为关键字: LOW_PRIORITY, DELAYED, HIGH_PRIORITY, IGNORE
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType
        If tokenType <> SqlTokenType.KEY_LOW_PRIORITY _
            And tokenType <> SqlTokenType.KEY_DELAYED _
            And tokenType <> SqlTokenType.KEY_HIGH_PRIORITY _
            And tokenType <> SqlTokenType.KEY_IGNORE Then

            GoTo outActionBreak
        End If

        tokenScanner.toNext
    Wend
outActionBreak:

    '  完成后，检查: 如果没有 token 了，则返回 null
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 4. 如果下一个 token 是 into，则跳过
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.KEY_INTO Then
        tokenScanner.toNext

        ' 完成后，检查: 如果没有 token 了，则返回 null
        If Not tokenScanner.hasNext Then
            ' return null
            Exit Function
        End If
    End If

    ' 5 如果下一个 token 是不是 str_name
    Set token = tokenScanner.peek
    ' 5.1 如果 token 不是 str_name，则返回 null
    If token.tokenType <> SqlTokenType.STR_NAME Then
        ' return null
        Exit Function
    End If

    ' 5.2 否则，初始化一个 [活动作用域] 对象
    Dim result As SqlTranslateSnippetsEnv
    Set result = New SqlTranslateSnippetsEnv
    result.Init SqlTokenType.KEY_INSERT

    ' 提前设置 return result, 以免后面重复设置
    Set doParseInsert = result

    ' 5.3 将 str_name 包装成表级作用域，并作为主表作用域保存到 [活动作用域]
    result.ISqlTranslateEnv_setMainEnv createRealTableEnv(token)

    ' 5.4 扫描器向后移动
    tokenScanner.toNext

    ' 5.5 检查，如果没有 token 了，则返回 [活动作用域]
    If Not tokenScanner.hasNext Then
        ' return result
        Exit Function
    End If

    ' 6. 匹配 insert 的各个组成部分
    ' 6.1 匹配后面可能会出现的 partition
    skipPartition tokenScanner

    ' 检查: 如果 `startNestCount > parenthesisNestCount`, 则退出，并返回 [活动作用域]
    If startNestCount > parenthesisNestCount Then
        ' return result
        Exit Function
    End If
    ' 如果没有 token 了, 则退出，并返回 [活动作用域]
    If Not tokenScanner.hasNext Then
        ' return result
        Exit Function
    End If

    ' 6.2 尝试匹配列名
    parseInsertColNames result, tokenScanner

    ' 检查: 如果 `startNestCount > parenthesisNestCount`, 则退出，并返回 [活动作用域]
    If startNestCount > parenthesisNestCount Then
        ' return result
        Exit Function
    End If
    ' 如果没有 token 了, 则退出，并返回 [活动作用域]
    If Not tokenScanner.hasNext Then
        ' return result
        Exit Function
    End If

    ' 6.3 尝试匹配 values
    skipInsertValues tokenScanner

    ' 检查: 如果 `startNestCount > parenthesisNestCount`, 则退出，并返回 [活动作用域]
    If startNestCount > parenthesisNestCount Then
        ' return result
        Exit Function
    End If
    ' 如果没有 token 了, 则退出，并返回 [活动作用域]
    If Not tokenScanner.hasNext Then
        ' return result
        Exit Function
    End If

    ' 6.4 尝试匹配可能存在的 Select 语句
    Dim selectEnv As ISqlTranslateEnv
    Set selectEnv = parseSelect(tokenScanner, startNestCount, False)
    result.ISqlTranslateEnv_addSubEnv selectEnv

    ' 检查: 如果 `startNestCount > parenthesisNestCount`, 则退出，并返回 [活动作用域]
    If startNestCount > parenthesisNestCount Then
        ' return result
        Exit Function
    End If
    ' 如果没有 token 了, 则退出，并返回 [活动作用域]
    If Not tokenScanner.hasNext Then
        ' return result
        Exit Function
    End If

    ' 6.5 尝试匹配 ON DUPLICATE KEY UPDATE 语句
    parseInsertDuplicate result, tokenScanner
End Function

' 解析 insert 中的插入列名
' 需要匹配的内容:
'   [(col_name [, col_name] ...)]
Private Sub parseInsertColNames(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 检查参数
    ' 2. 跳过第一个 [(],
    '     - 如果第一个 token 不是 [(]，则立刻结束
    '     - 如果是 [(]，则记录括号作用域深度，然后跳过 [(]
    ' 3. 匹配第一个 colName
    ' 4. 匹配剩余的 [, col_name]
    '     1. 如果遇到了 [,]，则跳过。否则退出
    '     2. 继续匹配 colName
    ' 5. 如果当前的括号深度 > nestCountBackup, 尝试退出末尾的 [)]

    ' 1. 检查参数
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 跳过第一个 [(],
    '     - 如果第一个 token 不是 [(]，则立刻结束
    '     - 如果是 [(]，则记录括号作用域深度，然后跳过 [(]
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.LEFT_PARENTHESIS Then
        Exit Sub
    End If
    tokenScanner.toNext

    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount
    parenthesisNestCount = parenthesisNestCount + 1

    ' 3. 匹配第一个 colName
    Dim colInfo As ISqlTranslateColInfo
    Set colInfo = parseColName(tokenScanner)
    env.addConditionColInfo colInfo

    ' 4. 匹配剩余的 [, col_name]
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        If token.tokenType = SqlTokenType.COMMA Then
            ' 4.1 如果遇到了 [,]，则跳过。否则退出
            '     这里为了兼容语法异常，尝试跳过多个连续出现的[,]
            Do
                tokenScanner.toNext

                If Not tokenScanner.hasNext Then
                    GoTo matchTokenBreak
                End If

                Set token = tokenScanner.peek
            Loop While token.tokenType = SqlTokenType.COMMA
        Else
            GoTo matchTokenBreak
        End If

        ' 4.2 继续匹配 colName
        Set colInfo = parseColName(tokenScanner)
        env.addConditionColInfo colInfo
    Wend
matchTokenBreak:

    ' 5. 如果当前的括号深度 > nestCountBackup, 尝试退出末尾的 [)]
    If parenthesisNestCount > nestCountBackup Then
        exitRightParenthesis tokenScanner, nestCountBackup
    End If
End Sub

' 跳过 insert 的 values
' 因为 insert values 中没有需要转换的列，所以这里不收集列，直接跳过
' 需要匹配的内容
'    {VALUES | VALUE} (value_list) [, (value_list)] ...
Private Sub skipInsertValues(ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 检查参数
    ' 2. 如果第一个 token 是 VALUES | VALUE 则跳过，否则立刻终止
    ' 3. 匹配第一组 (value_list)
    ' 4. 匹配: [, (value_list)]
    '     1. 如果遇到了 [,]，则跳过。否则立刻结束
    '     2. 匹配 (value_list)

    ' 1. 检查参数
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 如果第一个 token 是 VALUES | VALUE 则跳过，否则立刻终止
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_VALUES And token.tokenType <> SqlTokenType.KEY_VALUE Then
        Exit Sub
    End If
    tokenScanner.toNext

    ' 3. 匹配第一组 (value_list)
    skipInsertWrappedValueList tokenScanner

    ' 4. 匹配: [, (value_list)]
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        If token.tokenType = SqlTokenType.COMMA Then
            ' 4.1 如果遇到了 [,]，则跳过。否则立刻结束
            '     这里为了兼容语法异常，尝试跳过多个连续出现的[,]
            Do
                tokenScanner.toNext

                If Not tokenScanner.hasNext Then
                    GoTo matchTokenBreak
                End If

                Set token = tokenScanner.peek
            Loop While token.tokenType = SqlTokenType.COMMA
        Else
            GoTo matchTokenBreak
        End If

        ' 4.2 匹配 (value_list)
        skipInsertWrappedValueList tokenScanner
    Wend
matchTokenBreak:
End Sub

' 跳过 insert 中的 (value_list)
Private Sub skipInsertWrappedValueList(ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 检查参数
    ' 2. 如果下一个 token 不是 [(]，否则立刻终止
    ' 3. 记录开始时的括号嵌套深度 nestCountBackup
    ' 4. 括号深度 + 1，然后跳过
    ' 5. 开始遍历，搜索 [)]
    '     1. 如果 括号深度 = nestCountBackup，则立刻终止
    '     2. 如果遇到了 [;]，则立刻结束
    '     3. 如果遇到了 [(]，则括号深度 + 1
    '     4. 如果遇到了 [)]，则括号深度 - 1
    '     5. 其他内容全部跳过

    ' 1. 检查参数
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 如果下一个 token 不是 [(]，否则立刻终止
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.LEFT_PARENTHESIS Then
        Exit Sub
    End If

    ' 3. 记录开始时的括号嵌套深度 nestCountBackup
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount

    ' 4. 括号深度 + 1，然后跳过
    parenthesisNestCount = parenthesisNestCount + 1
    tokenScanner.toNext

    ' 5. 开始遍历，搜索 [)]
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        ' 5.1 如果 括号深度 = nestCountBackup，则立刻终止
        If nestCountBackup = parenthesisNestCount Then
            GoTo outParenthesisBreak
        End If

        Set token = tokenScanner.peek
        tokenType = token.tokenType

        If tokenType = SqlTokenType.SEMICOLON Then
            ' 5.2 如果遇到了 [;]，则立刻结束
            GoTo outParenthesisBreak
        ElseIf tokenType = SqlTokenType.LEFT_PARENTHESIS Then
            ' 5.3 如果遇到了 [(]，则括号深度 + 1
            parenthesisNestCount = parenthesisNestCount + 1
        ElseIf tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            ' 5.4 如果遇到了 [)]，则括号深度 - 1
            parenthesisNestCount = parenthesisNestCount - 1
        End If

        ' 5.5 其他内容全部跳过
        tokenScanner.toNext
    Wend
outParenthesisBreak:
End Sub

' 匹配 insert 的 DUPLICATE 语句
' 需要匹配的内容
'    ON DUPLICATE KEY UPDATE assignment_list
Private Sub parseInsertDuplicate(ByRef env As ISqlTranslateEnv, ByRef tokenScanner As ObjArrayRefScanner)
    ' 1. 检查参数
    ' 2. 检查后面的 token
    '     - 如果后面的 4 个 token 是: ON DUPLICATE KEY UPDATE，则继续
    '     - 否则，放回已经查看的 token，并结束
    ' 3. 匹配 assignment_list

    ' 1. 检查参数
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 检查后面的 token
    '     - 如果后面的 4 个 token 是: ON DUPLICATE KEY UPDATE ，则继续
    '     - 否则，放回已经查看的 token，并结束
    ' 2.1 ON
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_ON Then
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2.2 DUPLICATE
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_DUPLICATE Then
        tokenScanner.back
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2.3 KEY
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_KEY Then
        tokenScanner.back 2
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2.4 UPDATE
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_UPDATE Then
        tokenScanner.back 3
        Exit Sub
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 3. 匹配 assignment_list
    parseAssignmentList env, tokenScanner
End Sub

' ======================================================
' 解析 truncate
' ======================================================
Private Function parseTruncate(ByRef tokenScanner As ObjArrayRefScanner) As ISqlTranslateEnv
    ' 1. 检查参数
    ' 2. 如果不是 truncate，则返回 null
    ' 3. 如果下一个 token 是 table，则跳过
    ' 4. 如果下一个 token 是 str_name，则包装成真实表。如果不是则立刻停止

    ' 1. 检查参数
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 2. 如果不是 truncate，则返回 null
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_TRUNCATE Then
        ' return null
        Exit Function
    End If
    tokenScanner.toNext

    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 3. 如果下一个 token 是 table，则跳过
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.KEY_TABLE Then
        tokenScanner.toNext

        If Not tokenScanner.hasNext Then
            ' return null
            Exit Function
        End If

        Set token = tokenScanner.peek
    End If

    ' 4. 如果下一个 token 是 str_name，则
    '    跳过这个 token ，并将这个 token 包装成真实表
    If token.tokenType <> SqlTokenType.STR_NAME Then
        ' return null
        Exit Function
    End If
    tokenScanner.toNext

    Set parseTruncate = createRealTableEnv(token)
End Function

' ======================================================
' 解析 with
' ======================================================
' 循环解析处理可能会出现的 [(] + [with]
Private Sub parseMultiWith(ByRef tokenScanner As ObjArrayRefScanner)
    Dim token As SqlToken
    Dim tokenType As SqlTokenType

    ' IObjArrayRef<SqlTranslateWithEnv>
    Dim withEnvs As IObjArrayRef
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        tokenType = token.tokenType

        If token.tokenType = SqlTokenType.LEFT_PARENTHESIS Then
            ' 1.1 如果遇到 [(], 则括号嵌套深度 + 1
            parenthesisNestCount = parenthesisNestCount + 1
            tokenScanner.toNext
        ElseIf token.tokenType = SqlTokenType.KEY_WITH Then
            ' 1.2 如果遇到了 with，则尝试匹配一组 withEnvs
            Set withEnvs = parseWith(tokenScanner)

            ' 1.2.1 匹配完成后，退出连续出现的 [)]
            While tokenScanner.hasNext
                Set token = tokenScanner.peek

                If token.tokenType <> SqlTokenType.RIGHT_PARENTHESIS Then
                    GoTo outParenthesisBreak
                End If

                parenthesisNestCount = parenthesisNestCount - 1
                tokenScanner.toNext
            Wend
outParenthesisBreak:

            ' 1.2.2 退出后，将 withEnvs 保存到缓存中
            saveUnconsumedWithEnvs parenthesisNestCount, withEnvs
        Else
            ' 1.3 如果遇到了其他 token，则退出
            GoTo whileBreak
        End If
    Wend
whileBreak:
End Sub

' 解析 with 语句
' 需要匹配的内容
'  withStatement -->  WITH [RECURSIVE] withEnv[, withEnv]
'        withEnv --> cte_name [(col_name [, col_name] ...)] AS (subquery)
'
' 这里将会生成一个子程序的表级作用域
' 每个调用该方法的父级作用域应该将结果保持到非
'
' @return IObjArrayRef<SqlTranslateWithEnv>
Private Function parseWith(ByRef tokenScanner As ObjArrayRefScanner) As IObjArrayRef
    ' 1. 检查参数
    ' 2. 如果不是 with，则返回 null
    ' 3. 跳过可能出现的 RECURSIVE
    ' 3. 如果下一个不是 str_name，则返回 null
    ' 4. 如果下一个是 [(]，则匹配列名
    If Not tokenScanner.hasNext Then
        'return null
        Exit Function
    End If

    ' 2. 如果不是 with，则返回 null
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_WITH Then
        'return null
        Exit Function
    End If
    tokenScanner.toNext

    ' 3. 跳过可能出现的 RECURSIVE
    While tokenScanner.hasNext
        Set token = tokenScanner.peek
        If token.tokenType <> SqlTokenType.KEY_RECURSIVE Then
            GoTo outResursiveBreak
        End If

        tokenScanner.toNext
    Wend
outResursiveBreak:

    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 4. 尝试匹配一个 with 表定义
    Dim result As IObjArrayRef
    Dim withEnv As SqlTranslateWithEnv
    Set withEnv = parseWithEnv(tokenScanner)
    If Not withEnv Is Nothing Then
        Set result = UtilsArrayRef.createRealObjArrayRef(5)
        result.Add withEnv
    End If
    ' 即使这里匹配到了一个 null，也不立刻停止，以兼容部分语法异常

    ' 5. 匹配剩余的 [, withEnv]
    ' 5.0 遍历 token
    ' 5.1 如果遇到了一个 [,], 跳过，并继续；否则退出遍历
    ' 5.2 尝试匹配一个 withEnv
    '       如果 withEnv 不是 null，则保存
    '       如果 withEnv 是 null，暂不退出，继续处理，以兼容部分语法异常
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        ' 5.1 如果遇到了一个 [,], 跳过，并继续
        '     这里为了兼容异常，退出可能连续出现的多个 [,]
        If token.tokenType = SqlTokenType.COMMA Then
            Do
                tokenScanner.toNext

                If Not tokenScanner.hasNext Then
                    GoTo matchTokenBreak
                End If

                Set token = tokenScanner.peek
            Loop While token.tokenType = SqlTokenType.COMMA
        Else
            GoTo matchTokenBreak
        End If

        ' 5.2 尝试匹配一个 withEnv
        Set withEnv = parseWithEnv(tokenScanner)
        If Not withEnv Is Nothing Then
            If result Is Nothing Then
                Set result = UtilsArrayRef.createRealObjArrayRef(5)
            End If

            result.Add withEnv
        End If
    Wend
matchTokenBreak:

    Set parseWith = result
End Function

' 匹配一个 with 作用域
' 需要匹配的内容
'        withEnv --> cte_name [(col_name [, col_name] ...)] AS (subquery)
Private Function parseWithEnv(ByRef tokenScanner As ObjArrayRefScanner) As SqlTranslateWithEnv
    ' 1. 检查参数
    If Not tokenScanner.hasNext Then
        ' return null
        Exit Function
    End If

    ' 2. 如果下一个 token 不是 str_name, 则立刻结束
    '    如果 cte_name 后面存在列名: (col_name [, col_name] ...)
    '    则词法分析时会误将 cte_name 当作函数名，这里为了兼容，需要同时检查 STR_NAME + STR_FUNC
    '    并且如果是 STR_FUNC，应该手动修改成 STR_NAME
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.STR_FUNC Then
        token.tokenType = SqlTokenType.STR_NAME
    ElseIf token.tokenType <> SqlTokenType.STR_NAME Then
        ' return null
        Exit Function
    End If

    tokenScanner.toNext

    ' 保存 with 表名
    Dim result As SqlTranslateWithEnv
    Set result = New SqlTranslateWithEnv
    result.setWithName token.value

    ' 3. 尝试匹配 [(col_name [, col_name] ...)]
    parseWithColNames result, tokenScanner
    If Not tokenScanner.hasNext Then
        Set parseWithEnv = result
        Exit Function
    End If

    ' 4. 匹配下一个 As
    '    如果不是 As，则说明发生了语法异常，这里无法处理，立刻结束
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.KEY_AS Then
        Set parseWithEnv = result
        Exit Function
    End If
    tokenScanner.toNext

    ' 5. 匹配 with 表的查询内容
    '  (subquery)
    Dim subquery As ISqlTranslateEnv
    Set subquery = parseSelect(tokenScanner, parenthesisNestCount, False)
    result.ISqlTranslateEnv_setMainEnv subquery

    Set parseWithEnv = result
End Function

' 尝试匹配 with 中的列名
' 需要匹配的内容
'    (col_name [, col_name] ...)
Public Sub parseWithColNames(ByRef env As SqlTranslateWithEnv, ByRef tokenScanner As ObjArrayRefScanner)
    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 1. 如果第一个 token 不是 [(] ，则立刻结束
    Dim token As SqlToken
    Set token = tokenScanner.peek
    If token.tokenType <> SqlTokenType.LEFT_PARENTHESIS Then
        Exit Sub
    End If
    tokenScanner.toNext

    ' 备份开始时的括号嵌套深度
    Dim nestCountBackup As Integer
    nestCountBackup = parenthesisNestCount
    parenthesisNestCount = parenthesisNestCount + 1

    If Not tokenScanner.hasNext Then
        Exit Sub
    End If

    ' 2. 匹配一个 col_name
    Set token = tokenScanner.peek
    If token.tokenType = SqlTokenType.STR_NAME Then
        env.addWithColName token.value
        tokenScanner.toNext
    End If

    ' 3. 匹配剩余的 [, col_name]
    ' 3.0 遍历剩余 token
    ' 3.1 如果遇到了一个 [,], 跳过，并继续；否则退出遍历
    ' 3.2 如果遇到了一个 str_name, 则保存为 with 列名；否则退出遍历
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        ' 3.1 如果遇到了一个 [,], 跳过，并继续
        '     这里为了兼容异常，退出可能连续出现的多个 [,]
        If token.tokenType = SqlTokenType.COMMA Then
            Do
                tokenScanner.toNext

                If Not tokenScanner.hasNext Then
                    GoTo matchTokenBreak
                End If

                Set token = tokenScanner.peek
            Loop While token.tokenType = SqlTokenType.COMMA
        Else
            GoTo matchTokenBreak
        End If

        ' 3.2 如果遇到了一个 str_name, 则保存为 with 列名；否则退出遍历
        ' token = tokenScanner.peek() // 在 do...while 中已经获取完了
        If token.tokenType = SqlTokenType.STR_NAME Then
            env.addWithColName token.value

            tokenScanner.toNext
        Else
            GoTo matchTokenBreak
        End If
    Wend
matchTokenBreak:

    ' 4. 退出 [)]
    ' 4.0 遍历 token
    ' 4.1 如果括号作用域深度已经退出到了初始深度，则立刻退出
    ' 4.2 如果遇到了 [;]，则立刻结束
    ' 4.3 如果遇到了 [)]，则
    '      作用域深度 - 1
    '      跳过当前 token，然后回到 4.0 继续
    ' 4.4 如果遇到了 [(]，则  （这里为了防止语法异常）
    '      作用域深度 + 1
    '      跳过当前 token，然后回到 4.0 继续
    ' 4.5 如果遇到了其他内容，可能存在语法异常，这里全部忽略，然后回到 4.0 继续
    Dim tokenType As SqlTokenType
    While tokenScanner.hasNext
        ' 4.1 如果括号作用域深度已经退出到了初始深度，则立刻退出
        If nestCountBackup = parenthesisNestCount Then
            GoTo outParenthesisBreak
        End If

        Set token = tokenScanner.peek
        tokenType = token.tokenType

        ' 4.2 如果遇到了 [;]，则立刻结束
        If tokenType = SqlTokenType.SEMICOLON Then
            GoTo outParenthesisBreak
        End If

        If tokenType = SqlTokenType.RIGHT_PARENTHESIS Then
            ' 4.3 如果遇到了 [)]，则
            '      作用域深度 - 1
            '      跳过当前 token，然后回到 4.0 继续
            parenthesisNestCount = parenthesisNestCount - 1
        ElseIf tokenType = SqlTokenType.LEFT_PARENTHESIS Then
            ' 4.4 如果遇到了 [(]，则  （这里为了防止语法异常）
            '      作用域深度 + 1
            '      跳过当前 token，然后回到 4.0 继续
            parenthesisNestCount = parenthesisNestCount + 1
        End If

        ' 4.5 如果遇到了其他内容，可能存在语法异常，这里全部忽略，然后回到 4.0 继续
        tokenScanner.toNext
    Wend
outParenthesisBreak:
End Sub

' ======================================================
' 缓存 withEnv
' ======================================================
' 保存 withEnvs
Private Sub saveUnconsumedWithEnvs(ByVal nestCount As Integer, ByRef withEnvs As IObjArrayRef)
    If withEnvs Is Nothing Then
        Exit Sub
    End If

    ' 保证缓存已存在
    If unconsumedWithEnvsCache Is Nothing Then
        Set unconsumedWithEnvsCache = UtilsCreateSysObj.createMap
    End If

    ' 检查当前的嵌套深度在缓存中是否存在。如果不存在，则从对象池中获取一个对象栈
    Dim groupStack As ObjStack
    If unconsumedWithEnvsCache.Exists(nestCount) Then
        Set groupStack = unconsumedWithEnvsCache.Item(nestCount)
    Else
        Set groupStack = UtilsPool.getObjStack
        unconsumedWithEnvsCache.Add nestCount, groupStack
    End If

    ' 缓存当前这一组 withEnvs
    groupStack.push withEnvs
End Sub

' 将缓存中的所有未使用的 withEnv 作为[无归属 withEnv] 保存到指定作用域中
' 然后清空缓存
Private Sub alterUnconsumedWithEnvsToUncredited(ByRef env As ISqlTranslateEnv)
    If unconsumedWithEnvsCache Is Nothing Then
        Exit Sub
    End If

    If unconsumedWithEnvsCache.Count = 0 Then
        Exit Sub
    End If

    Dim k As Variant
    Dim stack As ObjStack
    Dim withEnvs As IObjArrayRef
    Dim i As Long
    For Each k In unconsumedWithEnvsCache
        Set stack = unconsumedWithEnvsCache.Item(k)

        ' 将栈中的数据对象全部保存到 env 中
        While stack.Count > 0
            Set withEnvs = stack.pop
            With withEnvs
                For i = 0 To .MaxIndex
                    env.addSubEnv .Item(i)
                Next
            End With
        Wend

        ' 将当前的栈对象归还给对象池
        UtilsPool.releaseObjStack stack
    Next

    ' 清空缓存
    unconsumedWithEnvsCache.RemoveAll
End Sub

' 检查缓存中是否存在未被使用的 withEnv
Private Function existsUnconsumedWithEnvs() As Boolean
    If unconsumedWithEnvsCache Is Nothing Then
        existsUnconsumedWithEnvs = False
        Exit Function
    End If

    existsUnconsumedWithEnvs = (unconsumedWithEnvsCache.Count <> 0)
End Function

' 检查指定深度下 withEnvs 的组数
Private Function getUnconsumedWithEnvsGroupCount(ByVal nestCount As Integer) As Long
    If unconsumedWithEnvsCache Is Nothing Then
        getUnconsumedWithEnvsGroupCount = 0
        Exit Function
    End If

    If Not unconsumedWithEnvsCache.Exists(nestCount) Then
        getUnconsumedWithEnvsGroupCount = 0
    Else
        getUnconsumedWithEnvsGroupCount = unconsumedWithEnvsCache.Item(nestCount).Count
    End If
End Function

' 从指定深度下抽取一组 withEnvs
'
' @return IObjArrayRef<SqlTranslateWithEnv>
Private Function popUnconsumedWithEnvs(ByVal nestCount As Integer) As IObjArrayRef
    If unconsumedWithEnvsCache Is Nothing Then
        'return null
        Exit Function
    End If

    If Not unconsumedWithEnvsCache.Exists(nestCount) Then
        'return null
        Exit Function
    End If

    ' 获取指定深度的对象栈，并弹出一组 withEnvs
    Dim groupStack As ObjStack
    Set groupStack = unconsumedWithEnvsCache.Item(nestCount)

    ' IObjArrayRef<SqlTranslateWithEnv>
    Dim result As IObjArrayRef
    Set result = groupStack.pop

    ' 如果当前对象栈已经为空，则从缓存删除，并将对象栈释放会对象池
    If groupStack.Count = 0 Then
        unconsumedWithEnvsCache.Remove nestCount
        UtilsPool.releaseObjStack groupStack
    End If

    Set popUnconsumedWithEnvs = result
End Function

' ======================================================
' 处理边处的连续括号
' ======================================================
' 消耗连续出现的 (
Private Sub consumeLeftParenthesis(ByRef tokenScanner As ObjArrayRefScanner)
    Dim token As SqlToken
    While tokenScanner.hasNext
        Set token = tokenScanner.peek

        If token.tokenType <> SqlTokenType.LEFT_PARENTHESIS Then
            Goto while1Break
        End If

        parenthesisNestCount = parenthesisNestCount + 1
        tokenScanner.toNext
    Wend
while1Break:
End Sub

' 退出 ), 并需要考虑 withEnv
'
' 退出连续出现的 [)]。每退出一层，则保存一层的 withEnv
' 注意:
'      只能处理退出 ) 之后的这一层的 withEnv
'      作用域解析完成之后，第一个 [)] 之前的这一级的 withEnv 需要手动处理
'
' @return minNestCount 退出时，parenthesisNestCount 容许的最小嵌套深度。如果值是负数，将会一直退出直到一个不是 [)] 的 token
Private Function exitRightParenthesisAndWithEnv(ByRef tokenScanner As ObjArrayRefScanner, _
    ByRef env As ISqlTranslateEnv, Optional ByVal minNestCount As Integer = -1000) As ISqlTranslateEnv
    Dim token As SqlToken
    While tokenScanner.hasNext
        ' 如果到达的容许的最小值，则直接退出
        If parenthesisNestCount <= minNestCount Then
            GoTo exitParenthesisBreak
        End If

        Set token = tokenScanner.peek
        If token.tokenType <> SqlTokenType.RIGHT_PARENTHESIS Then
            GoTo exitParenthesisBreak
        End If

        ' 跳过当前 [)]
        tokenScanner.toNext

        ' 括号嵌套深度 - 1
        parenthesisNestCount = parenthesisNestCount - 1

        ' 检查: 如果当括号嵌套深度别下是存在可用的 withEnvs
        '       则将这一深度的所有 withEnvs 都保存到 [活动作用域]
        Set env = saveWithEnvsToEnv(env, parenthesisNestCount, True)
    Wend
exitParenthesisBreak:

    Set exitRightParenthesisAndWithEnv = env
End Function

' 退出连续出现的 [)], 不需要 withEnv
'
' @return minNestCount 退出时，parenthesisNestCount 容许的最小嵌套深度。如果值是负数，将会一直退出直到一个不是 [)] 的 token
Public Sub exitRightParenthesis(ByRef tokenScanner As ObjArrayRefScanner, Optional ByVal minNestCount As Integer = -1000)
    Dim token As SqlToken
    While tokenScanner.hasNext
        ' 如果到达的容许的最小值，则直接退出
        If parenthesisNestCount <= minNestCount Then
            GoTo whileBreak
        End If

        Set token = tokenScanner.peek()
        If token.tokenType <> SqlTokenType.RIGHT_PARENTHESIS Then
            GoTo whileBreak
        End If

        ' 跳过当前 [)]
        tokenScanner.toNext

        ' 括号嵌套深度 - 1
        parenthesisNestCount = parenthesisNestCount - 1
    Wend
whileBreak:
End Sub